Skip to content

Commit d47155f

Browse files
committed
GH-5676 shacl sail customizable inference
1 parent 077ba80 commit d47155f

18 files changed

Lines changed: 778 additions & 65 deletions

File tree

core/model-vocabulary/src/main/java/org/eclipse/rdf4j/model/vocabulary/RSX.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public class RSX {
4040
/** <var>http://rdf4j.org/shacl-extensions#targetShape</var> */
4141
public final static IRI targetShape = create("targetShape");
4242

43+
public final static IRI rdfsSubClassReasoning = create("rdfsSubClassReasoning");
44+
public final static IRI includeInferredStatements = create("includeInferredStatements");
45+
4346
public final static IRI dataGraph = create("dataGraph");
4447
public final static IRI shapesGraph = create("shapesGraph");
4548

core/sail/inferencer/src/main/java/org/eclipse/rdf4j/sail/inferencer/fc/SchemaCachingRDFSInferencerConnection.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ void processForSchemaCache(Statement statement) {
114114
sail.addSubPropertyOfStatement(
115115
sail.getValueFactory().createStatement(subject, RDFS.SUBPROPERTYOF, RDFS.MEMBER));
116116
schemaChange = true;
117-
} else if (predicate.equals(RDF.TYPE)) {
117+
} else if (predicate.equals(RDF.TYPE) && object instanceof Resource) {
118118
if (!sail.hasType(((Resource) object))) {
119119
sail.addType((Resource) object);
120120
schemaChange = true;
@@ -276,11 +276,7 @@ private void addStatement(boolean actuallyAdd, Resource subject, IRI predicate,
276276

277277
}
278278

279-
if (predicate.equals(RDF.TYPE)) {
280-
if (!(object instanceof Resource)) {
281-
throw new SailException("Expected object to a a Resource: " + object.toString());
282-
}
283-
279+
if (predicate.equals(RDF.TYPE) && object instanceof Resource) {
284280
sail.resolveTypes((Resource) object)
285281
.stream()
286282
.peek(inferredType -> {

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailBaseConfiguration.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ abstract class ShaclSailBaseConfiguration extends NotifyingSailWrapper {
3535
private boolean validationEnabled = ShaclSailConfig.VALIDATION_ENABLED_DEFAULT;
3636
private boolean cacheSelectNodes = ShaclSailConfig.CACHE_SELECT_NODES_DEFAULT;
3737
private boolean rdfsSubClassReasoning = ShaclSailConfig.RDFS_SUB_CLASS_REASONING_DEFAULT;
38+
private boolean includeInferredStatements = false;
3839
private boolean serializableValidation = ShaclSailConfig.SERIALIZABLE_VALIDATION_DEFAULT;
3940
private boolean performanceLogging = ShaclSailConfig.PERFORMANCE_LOGGING_DEFAULT;
4041
private boolean eclipseRdf4jShaclExtensions = ShaclSailConfig.ECLIPSE_RDF4J_SHACL_EXTENSIONS_DEFAULT;
@@ -146,6 +147,25 @@ public void setRdfsSubClassReasoning(boolean rdfsSubClassReasoning) {
146147
this.rdfsSubClassReasoning = rdfsSubClassReasoning;
147148
}
148149

150+
/**
151+
* Check if inferred statements from the base sail should be used during SHACL validation when
152+
* {@link #isRdfsSubClassReasoning()} is disabled.
153+
*
154+
* @return <code>true</code> if inferred statements should be considered, <code>false</code> otherwise.
155+
*/
156+
public boolean isIncludeInferredStatements() {
157+
return includeInferredStatements;
158+
}
159+
160+
/**
161+
* Allow SHACL validation to use inferred statements from the base sail when RDFS subclass reasoning is disabled.
162+
*
163+
* @param includeInferredStatements default false
164+
*/
165+
public void setIncludeInferredStatements(boolean includeInferredStatements) {
166+
this.includeInferredStatements = includeInferredStatements;
167+
}
168+
149169
/**
150170
* Disable the SHACL validation on commit()
151171
*/

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.eclipse.rdf4j.sail.memory.MemoryStore;
5252
import org.eclipse.rdf4j.sail.shacl.ShaclSail.TransactionSettings.ValidationApproach;
5353
import org.eclipse.rdf4j.sail.shacl.ast.ContextWithShape;
54+
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
5455
import org.eclipse.rdf4j.sail.shacl.results.ValidationReport;
5556
import org.eclipse.rdf4j.sail.shacl.results.lazy.LazyValidationReport;
5657
import org.eclipse.rdf4j.sail.shacl.results.lazy.ValidationResultIterator;
@@ -497,20 +498,24 @@ private ValidationReport validate(List<ContextWithShape> shapes, boolean validat
497498

498499
try {
499500
try (ConnectionsGroup connectionsGroup = getConnectionsGroup()) {
500-
return performValidation(shapes, validateEntireBaseSail, connectionsGroup);
501+
return performValidation(shapes, validateEntireBaseSail, connectionsGroup, this,
502+
previousStateConnection);
501503
}
502504
} finally {
503505
rdfsSubClassOfReasoner = null;
504506
}
505507

506508
}
507509

508-
void prepareValidation(ValidationSettings validationSettings) throws InterruptedException {
510+
void prepareValidation(ValidationSettings validationSettings, boolean requireRdfsSubClassReasoning)
511+
throws InterruptedException {
509512

510513
assert isValidationEnabled();
511514

512-
if (sail.isRdfsSubClassReasoning()) {
515+
if (requireRdfsSubClassReasoning) {
513516
rdfsSubClassOfReasoner = RdfsSubClassOfReasoner.createReasoner(this, validationSettings);
517+
} else {
518+
rdfsSubClassOfReasoner = null;
514519
}
515520

516521
if (sail.isShutdown()) {
@@ -528,15 +533,34 @@ void prepareValidation(ValidationSettings validationSettings) throws Interrupted
528533
}
529534

530535
ConnectionsGroup getConnectionsGroup() {
536+
return getConnectionsGroup(this, previousStateConnection, sail.isIncludeInferredStatements(),
537+
sail.isRdfsSubClassReasoning());
538+
}
539+
540+
ConnectionsGroup getConnectionsGroup(SailConnection baseConnection, SailConnection previousStateConnection,
541+
boolean includeInferredStatements, boolean useRdfsSubClassReasoning) {
542+
RdfsSubClassOfReasoner reasoner = useRdfsSubClassReasoning ? rdfsSubClassOfReasoner : null;
543+
ConnectionsGroup.RdfsSubClassOfReasonerProvider provider = reasoner == null ? null : () -> reasoner;
531544

532-
return new ConnectionsGroup(new VerySimpleRdfsBackwardsChainingConnection(this, rdfsSubClassOfReasoner),
545+
return new ConnectionsGroup(
546+
new VerySimpleRdfsBackwardsChainingConnection(baseConnection, reasoner, includeInferredStatements),
533547
previousStateConnection, addedStatements, removedStatements, stats,
534-
this::getRdfsSubClassOfReasoner, transactionSettings, sail.sparqlValidation);
548+
provider, transactionSettings, sail.sparqlValidation);
549+
}
550+
551+
private boolean requiresRdfsSubClassReasoner(List<ContextWithShape> shapes) {
552+
return shapes.stream()
553+
.map(ContextWithShape::getShape)
554+
.map(Shape::getRdfsSubClassReasoningOverride)
555+
.anyMatch(Boolean.TRUE::equals);
535556
}
536557

537558
private ValidationReport performValidation(List<ContextWithShape> shapes, boolean validateEntireBaseSail,
538-
ConnectionsGroup connectionsGroup) throws InterruptedException {
559+
ConnectionsGroup connectionsGroup, SailConnection baseConnection, SailConnection previousStateConnection)
560+
throws InterruptedException {
539561
long beforeValidation = 0;
562+
boolean defaultIncludeInferredStatements = sail.isIncludeInferredStatements();
563+
boolean defaultRdfsSubClassReasoning = sail.isRdfsSubClassReasoning();
540564

541565
if (sail.isPerformanceLogging()) {
542566
beforeValidation = System.currentTimeMillis();
@@ -547,18 +571,36 @@ private ValidationReport performValidation(List<ContextWithShape> shapes, boolea
547571

548572
Stream<Callable<ValidationResultIterator>> callableStream = shapes
549573
.stream()
550-
.map(contextWithShapes -> new ShapeValidationContainer(
551-
contextWithShapes.getShape(),
552-
() -> contextWithShapes.getShape()
553-
.generatePlans(connectionsGroup,
554-
new ValidationSettings(contextWithShapes.getDataGraph(),
555-
sail.isLogValidationPlans(), validateEntireBaseSail,
556-
sail.isPerformanceLogging())),
557-
sail.isGlobalLogValidationExecution(), sail.isLogValidationViolations(),
558-
sail.getEffectiveValidationResultsLimitPerConstraint(), sail.isPerformanceLogging(),
559-
sail.isLogValidationPlans(),
560-
logger,
561-
connectionsGroup))
574+
.map(contextWithShapes -> {
575+
Shape shape = contextWithShapes.getShape();
576+
boolean shapeRdfsSubClassReasoning = shape
577+
.usesRdfsSubClassReasoning(defaultRdfsSubClassReasoning);
578+
boolean shapeIncludeInferredStatements = shape
579+
.usesIncludeInferredStatements(defaultIncludeInferredStatements);
580+
581+
boolean closeConnectionsGroup = false;
582+
ConnectionsGroup shapeConnectionsGroup = connectionsGroup;
583+
if (shapeRdfsSubClassReasoning != defaultRdfsSubClassReasoning
584+
|| shapeIncludeInferredStatements != defaultIncludeInferredStatements) {
585+
shapeConnectionsGroup = getConnectionsGroup(baseConnection, previousStateConnection,
586+
shapeIncludeInferredStatements, shapeRdfsSubClassReasoning);
587+
closeConnectionsGroup = true;
588+
}
589+
ConnectionsGroup planConnectionsGroup = shapeConnectionsGroup;
590+
591+
return new ShapeValidationContainer(
592+
shape,
593+
() -> shape.generatePlans(planConnectionsGroup,
594+
new ValidationSettings(contextWithShapes.getDataGraph(),
595+
sail.isLogValidationPlans(), validateEntireBaseSail,
596+
sail.isPerformanceLogging())),
597+
sail.isGlobalLogValidationExecution(), sail.isLogValidationViolations(),
598+
sail.getEffectiveValidationResultsLimitPerConstraint(), sail.isPerformanceLogging(),
599+
sail.isLogValidationPlans(),
600+
logger,
601+
shapeConnectionsGroup,
602+
closeConnectionsGroup);
603+
})
562604

563605
.filter(ShapeValidationContainer::hasPlanNode)
564606
.peek(s -> {
@@ -1071,24 +1113,28 @@ public void prepare() throws SailException {
10711113
return;
10721114
}
10731115

1116+
List<ContextWithShape> shapesToValidate = shapesAfterRefresh != null ? shapesAfterRefresh : currentShapes;
1117+
boolean requiresRdfsSubClassReasoner = sail.isRdfsSubClassReasoning()
1118+
|| requiresRdfsSubClassReasoner(shapesToValidate);
1119+
10741120
stats.setEmptyIncludingCurrentTransaction(ConnectionHelper.isEmpty(this));
10751121

10761122
prepareValidation(
1077-
new ValidationSettings(null, sail.isLogValidationPlans(), false, sail.isPerformanceLogging()));
1123+
new ValidationSettings(null, sail.isLogValidationPlans(), false, sail.isPerformanceLogging()),
1124+
requiresRdfsSubClassReasoner);
10781125

10791126
ValidationReport invalidTuples = null;
10801127
if (useSerializableValidation) {
10811128
synchronized (sail.singleConnectionMonitor) {
10821129
if (!sail.usesSingleConnection()) {
1083-
invalidTuples = serializableValidation(
1084-
shapesAfterRefresh != null ? shapesAfterRefresh : currentShapes);
1130+
invalidTuples = serializableValidation(shapesToValidate);
10851131
}
10861132
}
10871133
}
10881134

10891135
if (invalidTuples == null) {
10901136
invalidTuples = validate(
1091-
shapesAfterRefresh != null ? shapesAfterRefresh : currentShapes,
1137+
shapesToValidate,
10921138
shapesModifiedInCurrentTransaction || isBulkValidation());
10931139
}
10941140

@@ -1152,10 +1198,8 @@ private boolean isBulkValidation() {
11521198
private ValidationReport serializableValidation(List<ContextWithShape> shapesAfterRefresh)
11531199
throws InterruptedException {
11541200
try {
1155-
try (ConnectionsGroup connectionsGroup = new ConnectionsGroup(
1156-
new VerySimpleRdfsBackwardsChainingConnection(serializableConnection, rdfsSubClassOfReasoner), null,
1157-
addedStatements, removedStatements, stats, this::getRdfsSubClassOfReasoner, transactionSettings,
1158-
sail.sparqlValidation)) {
1201+
try (ConnectionsGroup connectionsGroup = getConnectionsGroup(serializableConnection, null,
1202+
sail.isIncludeInferredStatements(), sail.isRdfsSubClassReasoning())) {
11591203

11601204
connectionsGroup.getBaseConnection().begin(IsolationLevels.SNAPSHOT);
11611205
// actually force a transaction to start
@@ -1176,7 +1220,7 @@ private ValidationReport serializableValidation(List<ContextWithShape> shapesAft
11761220
serializableConnection.flush();
11771221

11781222
return performValidation(shapesAfterRefresh, shapesModifiedInCurrentTransaction || isBulkValidation(),
1179-
connectionsGroup);
1223+
connectionsGroup, serializableConnection, null);
11801224

11811225
} finally {
11821226
serializableConnection.rollback();

0 commit comments

Comments
 (0)