From 1b80a4a8a8d9f3e7cae81de6fd471e5b259940eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 1 Apr 2024 10:12:05 +0200 Subject: [PATCH 1/4] GH-1112 initial implementation for sh:closed --- core/sail/shacl/pom.xml | 2 +- .../eclipse/rdf4j/sail/shacl/ShaclSail.java | 2 + .../shacl/ast/CanProduceValidationReport.java | 20 ++ .../rdf4j/sail/shacl/ast/NodeShape.java | 5 - .../rdf4j/sail/shacl/ast/PropertyShape.java | 7 +- .../eclipse/rdf4j/sail/shacl/ast/Shape.java | 19 +- .../sail/shacl/ast/StatementMatcher.java | 14 + .../AbstractConstraintComponent.java | 4 - .../AbstractPairwiseConstraintComponent.java | 32 +- .../ClassConstraintComponent.java | 14 +- .../ClosedConstraintComponent.java | 282 +++++++++++++++++- .../ConstraintComponent.java | 2 - .../DisjointConstraintComponent.java | 2 +- .../EqualsConstraintComponent.java | 2 +- .../LessThanConstraintComponent.java | 2 +- .../LessThanOrEqualsConstraintComponent.java | 2 +- .../SparqlConstraintComponent.java | 19 +- .../VoidConstraintComponent.java | 98 ++++++ .../rdf4j/sail/shacl/ast/paths/Path.java | 2 +- .../sail/shacl/ast/paths/SimplePath.java | 2 +- .../planNodes/AbstractPairwisePlanNode.java | 32 +- .../planNodes/BulkedExternalInnerJoin.java | 2 +- .../BulkedExternalLeftOuterJoin.java | 1 + ...DisjointValuesBasedOnPathAndPredicate.java | 5 +- ...ckEqualsValuesBasedOnPathAndPredicate.java | 5 +- ...nOrEqualValuesBasedOnPathAndPredicate.java | 4 +- ...LessThanValuesBasedOnPathAndPredicate.java | 5 +- .../shacl/ast/planNodes/DatatypeFilter.java | 6 +- .../ast/planNodes/ExternalFilterByQuery.java | 37 ++- .../shacl/ast/planNodes/FilterPlanNode.java | 38 ++- .../ast/planNodes/FilterTargetIsObject.java | 4 +- .../ast/planNodes/FilterTargetIsSubject.java | 4 +- .../shacl/ast/planNodes/LanguageInFilter.java | 6 +- .../planNodes/LiteralComparatorFilter.java | 4 +- .../shacl/ast/planNodes/MaxLengthFilter.java | 4 +- .../shacl/ast/planNodes/MinLengthFilter.java | 4 +- .../shacl/ast/planNodes/NodeKindFilter.java | 4 +- .../shacl/ast/planNodes/PatternFilter.java | 4 +- .../ast/planNodes/SparqlConstraintSelect.java | 12 +- .../shacl/ast/planNodes/UnorderedSelect.java | 30 +- .../shacl/ast/planNodes/ValidationTuple.java | 33 +- .../shacl/ast/planNodes/ValueInFilter.java | 4 +- .../shacl/ast/targets/DashAllObjects.java | 2 +- .../shacl/ast/targets/DashAllSubjects.java | 2 +- .../shacl/ast/targets/EffectiveTarget.java | 7 +- .../shacl/ast/targets/RSXTargetShape.java | 2 +- .../sail/shacl/ast/targets/SparqlTarget.java | 2 +- .../sail/shacl/ast/targets/TargetClass.java | 2 +- .../shacl/ast/targets/TargetObjectsOf.java | 2 +- .../shacl/ast/targets/TargetSubjectsOf.java | 3 +- .../sail/shacl/results/ValidationResult.java | 6 +- .../results/lazy/LazyValidationReport.java | 1 - .../lazy/ValidationResultIterator.java | 7 +- .../rdf4j/sail/shacl/AbstractShaclTest.java | 92 +++--- .../rdf4j/sail/shacl/W3cComplianceTest.java | 2 - .../closed/complex/invalid/case1/query1.rq | 15 + .../closed/complex/invalid/case1/report.ttl | 42 +++ .../closed/complex/invalid/case2/query1.rq | 13 + .../closed/complex/invalid/case2/query2.rq | 10 + .../closed/complex/invalid/case2/report.ttl | 42 +++ .../closed/complex/invalid/case3/query1.rq | 13 + .../closed/complex/invalid/case3/query2.rq | 13 + .../closed/complex/invalid/case3/report.ttl | 58 ++++ .../closed/complex/invalid/case4/query1.rq | 14 + .../closed/complex/invalid/case4/query2.rq | 12 + .../closed/complex/invalid/case4/report.ttl | 42 +++ .../test-cases/closed/complex/shacl.trig | 37 +++ .../closed/complex/valid/case1/query1.rq | 13 + .../closed/complex/valid/case1/report.ttl | 12 + .../closed/complex/valid/case2/query1.rq | 13 + .../closed/complex/valid/case2/query2.rq | 10 + .../closed/complex/valid/case2/report.ttl | 12 + .../closed/complex/valid/case3/query1.rq | 13 + .../closed/complex/valid/case3/query2.rq | 10 + .../closed/complex/valid/case3/report.ttl | 12 + .../propertyShape/invalid/case1/query1.rq | 17 ++ .../propertyShape/invalid/case1/report.ttl | 43 +++ .../propertyShape/invalid/case2/query1.rq | 16 + .../propertyShape/invalid/case2/query2.rq | 10 + .../propertyShape/invalid/case2/report.ttl | 43 +++ .../propertyShape/invalid/case3/query1.rq | 16 + .../propertyShape/invalid/case3/query2.rq | 13 + .../propertyShape/invalid/case3/report.ttl | 60 ++++ .../propertyShape/invalid/case4/query1.rq | 17 ++ .../propertyShape/invalid/case4/query2.rq | 12 + .../propertyShape/invalid/case4/report.ttl | 43 +++ .../propertyShape/invalid/case5/query1.rq | 19 ++ .../propertyShape/invalid/case5/query2.rq | 10 + .../propertyShape/invalid/case5/report.ttl | 43 +++ .../propertyShape/invalid/case6/query1.rq | 20 ++ .../propertyShape/invalid/case6/query2.rq | 10 + .../propertyShape/invalid/case6/report.ttl | 52 ++++ .../closed/propertyShape/shacl.trig | 40 +++ .../propertyShape/valid/case1/query1.rq | 15 + .../propertyShape/valid/case1/report.ttl | 12 + .../propertyShape/valid/case2/query1.rq | 15 + .../propertyShape/valid/case2/query2.rq | 10 + .../propertyShape/valid/case2/report.ttl | 12 + .../propertyShape/valid/case3/query1.rq | 16 + .../propertyShape/valid/case3/query2.rq | 10 + .../propertyShape/valid/case3/report.ttl | 12 + .../closed/simple/invalid/case1/query1.rq | 15 + .../closed/simple/invalid/case1/report.ttl | 33 ++ .../closed/simple/invalid/case2/query1.rq | 13 + .../closed/simple/invalid/case2/query2.rq | 10 + .../closed/simple/invalid/case2/report.ttl | 33 ++ .../closed/simple/invalid/case3/query1.rq | 13 + .../closed/simple/invalid/case3/query2.rq | 13 + .../closed/simple/invalid/case3/report.ttl | 41 +++ .../closed/simple/invalid/case4/query1.rq | 14 + .../closed/simple/invalid/case4/query2.rq | 12 + .../closed/simple/invalid/case4/report.ttl | 33 ++ .../test-cases/closed/simple/shacl.trig | 29 ++ .../closed/simple/valid/case1/query1.rq | 13 + .../closed/simple/valid/case1/report.ttl | 12 + .../closed/simple/valid/case2/query1.rq | 13 + .../closed/simple/valid/case2/query2.rq | 10 + .../closed/simple/valid/case2/report.ttl | 12 + .../closed/simple/valid/case3/query1.rq | 13 + .../closed/simple/valid/case3/query2.rq | 10 + .../closed/simple/valid/case3/report.ttl | 12 + .../complex/mms/valid/case1/query1.rq | 14 +- 122 files changed, 2064 insertions(+), 206 deletions(-) create mode 100644 core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/CanProduceValidationReport.java create mode 100644 core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/report.ttl diff --git a/core/sail/shacl/pom.xml b/core/sail/shacl/pom.xml index d26ee58e5bf..db99b43fde0 100644 --- a/core/sail/shacl/pom.xml +++ b/core/sail/shacl/pom.xml @@ -66,7 +66,7 @@ org.topbraid shacl - 1.3.2 + 1.4.3 test diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java index d366593311a..f2b75e528ed 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java @@ -335,6 +335,8 @@ public static List getSupportedShaclPredicates() { SHACL.EQUALS, SHACL.LESS_THAN, SHACL.LESS_THAN_OR_EQUALS, + SHACL.CLOSED, + SHACL.IGNORED_PROPERTIES, DASH.hasValueIn, RSX.targetShape, RSX.dataGraph, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/CanProduceValidationReport.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/CanProduceValidationReport.java new file mode 100644 index 00000000000..767bfffe53e --- /dev/null +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/CanProduceValidationReport.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2024 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + ******************************************************************************/ + +package org.eclipse.rdf4j.sail.shacl.ast; + +public interface CanProduceValidationReport { + + void setProducesValidationReport(boolean producesValidationReport); + + boolean producesValidationReport(); + +} diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/NodeShape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/NodeShape.java index d74a61101ea..6ec57c5e1f8 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/NodeShape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/NodeShape.java @@ -239,11 +239,6 @@ public ConstraintComponent deepClone() { return nodeShape; } - @Override - public boolean overrideValidationReport() { - return false; - } - @Override public SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable subject, Variable object, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java index fba478384eb..2efcdcb1ac0 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/PropertyShape.java @@ -231,7 +231,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections .generateTransactionalValidationPlan(connectionsGroup, validationSettings, overrideTargetNode, Scope.propertyShape); - if (!(constraintComponent instanceof PropertyShape) && !constraintComponent.overrideValidationReport()) { + if (produceValidationReports) { validationPlanNode = new ValidationReportNode(validationPlanNode, t -> { return new ValidationResult(t.getActiveTarget(), t.getValue(), this, constraintComponent, getSeverity(), t.getScope(), t.getContexts(), @@ -315,11 +315,6 @@ public ConstraintComponent deepClone() { return nodeShape; } - @Override - public boolean overrideValidationReport() { - return false; - } - @Override public SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable subject, Variable object, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java index fc2e5897057..fb16757fb67 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java @@ -72,6 +72,7 @@ import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.QualifiedMinCountConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.SparqlConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.UniqueLangConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.VoidConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.XoneConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; @@ -295,7 +296,7 @@ List getConstraintComponents(ShaclProperties properties, Sh if (properties.isClosed()) { constraintComponent.add(new ClosedConstraintComponent(shapeSource, properties.getProperty(), - properties.getIgnoredProperties())); + properties.getIgnoredProperties(), this)); } for (IRI iri : properties.getClazz()) { @@ -379,6 +380,10 @@ List getConstraintComponents(ShaclProperties properties, Sh constraintComponent.add(component); } + if (constraintComponent.isEmpty()) { + constraintComponent.add(new VoidConstraintComponent()); + } + return constraintComponent; } @@ -608,11 +613,17 @@ private static void calculateIfProducesValidationResult(List split) { } } - propertyShape.produceValidationReports = true; + if (propertyShape.constraintComponents.get(0) instanceof CanProduceValidationReport) { + ((CanProduceValidationReport) propertyShape.constraintComponents.get(0)) + .setProducesValidationReport(true); + } else { + propertyShape.produceValidationReports = true; + } } else if (shape instanceof NodeShape) { - if (shape.constraintComponents.get(0) instanceof SparqlConstraintComponent) { - ((SparqlConstraintComponent) shape.constraintComponents.get(0)).produceValidationReports = true; + if (shape.constraintComponents.get(0) instanceof CanProduceValidationReport) { + ((CanProduceValidationReport) shape.constraintComponents.get(0)) + .setProducesValidationReport(true); } else { shape.produceValidationReports = true; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java index 3eb86c014a6..ec04e8e02b5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/StatementMatcher.java @@ -322,8 +322,22 @@ public int hashCode() { return Objects.hash(subject, predicate, object); } + private final static String VALUES_TARGET_0 = "VALUES ( ?target_0000000000 ){}\n"; + private final static String VALUES_TARGET_0_AND_1 = "VALUES ( ?target_0000000000 ?target_0000000001 ){}\n"; + public String getSparqlValuesDecl(Set varNamesRestriction, boolean addInheritedVarNames, Set varNamesInQueryFragment) { + + // EffectiveTarget interns all the first 1000 variable names, so we can use == to compare them + if (subject.name == "target_0000000000" && predicate.name == null && object.name == null + && varNamesRestriction.contains(subject.name) && varNamesInQueryFragment.contains(subject.name)) { + return VALUES_TARGET_0; + } else if (subject.name == "target_0000000000" && predicate.name == null && object.name == "target_0000000001" + && varNamesRestriction.contains(subject.name) && varNamesInQueryFragment.contains(subject.name) + && varNamesRestriction.contains(object.name) && varNamesInQueryFragment.contains(object.name)) { + return VALUES_TARGET_0_AND_1; + } + StringBuilder sb = new StringBuilder("VALUES ( "); if (subject.name != null && varNamesRestriction.contains(subject.name) || subject.baseName != null && varNamesRestriction.contains(subject.baseName)) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java index b58a4a8ea91..9702e6e6c73 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractConstraintComponent.java @@ -174,8 +174,4 @@ static PlanNode getAllTargetsIncludingThoseAddedByPath(ConnectionsGroup connecti return allTargets; } - @Override - public boolean overrideValidationReport() { - return false; - } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java index b299317689f..5c00a066fbe 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/AbstractPairwiseConstraintComponent.java @@ -11,20 +11,18 @@ package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; -import java.util.List; import java.util.Optional; import java.util.Set; import org.eclipse.rdf4j.model.IRI; -import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.CanProduceValidationReport; import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; -import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; import org.eclipse.rdf4j.sail.shacl.ast.paths.Path; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; @@ -39,10 +37,12 @@ import org.eclipse.rdf4j.sail.shacl.ast.targets.TargetChain; import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup; -abstract class AbstractPairwiseConstraintComponent extends AbstractConstraintComponent { +abstract class AbstractPairwiseConstraintComponent extends AbstractConstraintComponent + implements CanProduceValidationReport { final Shape shape; final IRI predicate; + boolean producesValidationReport; public AbstractPairwiseConstraintComponent(IRI predicate, Shape shape) { this.predicate = predicate; @@ -139,12 +139,12 @@ private PlanNode getAllTargetsBasedOnPredicate(ConnectionsGroup connectionsGroup PlanNode addedByPredicate = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, predicate, null, validationSettings.getDataGraph(), (s, d) -> { return new ValidationTuple(s.getSubject(), Scope.propertyShape, false, d); - }); + }, null); PlanNode removedByPredicate = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, predicate, null, validationSettings.getDataGraph(), (s, d) -> { return new ValidationTuple(s.getSubject(), Scope.propertyShape, false, d); - }); + }, null); PlanNode targetFilter1 = effectiveTarget.getTargetFilter(connectionsGroup, validationSettings.getDataGraph(), addedByPredicate); @@ -168,8 +168,8 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // removed statements that match predicate could affect sh:or if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedPredicates = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, - predicate, - null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.propertyShape)); + predicate, null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.propertyShape), null); deletedPredicates = getTargetChain() .getEffectiveTarget(Scope.propertyShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -187,7 +187,8 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // added statements that match predicate could affect sh:not if (connectionsGroup.getStats().hasAdded()) { PlanNode addedPredicates = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, predicate, - null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.propertyShape)); + null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.propertyShape), + null); addedPredicates = getTargetChain() .getEffectiveTarget(Scope.propertyShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -212,7 +213,7 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedPredicates = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, predicate, null, - dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), null); deletedPredicates = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -231,7 +232,7 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] if (connectionsGroup.getStats().hasAdded()) { PlanNode addedPredicates = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, predicate, null, - dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), null); addedPredicates = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -280,7 +281,12 @@ public int hashCode() { } @Override - public boolean overrideValidationReport() { - return true; + public void setProducesValidationReport(boolean producesValidationReport) { + this.producesValidationReport = producesValidationReport; + } + + @Override + public boolean producesValidationReport() { + return producesValidationReport; } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java index 2d643b3d094..6e9224cdff4 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java @@ -110,7 +110,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedTypes = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, RDF.TYPE, clazz, validationSettings.getDataGraph(), - UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), null); deletedTypes = getTargetChain() .getEffectiveTarget(Scope.nodeShape, @@ -176,7 +176,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedTypes = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, RDF.TYPE, clazz, validationSettings.getDataGraph(), - UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope)); + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), null); deletedTypes = getTargetChain() .getEffectiveTarget(scope, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -216,7 +216,8 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // removed type statements that match clazz could affect sh:or if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedTypes = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, RDF.TYPE, - clazz, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + clazz, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), + null); deletedTypes = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -233,7 +234,8 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // added type statements that match clazz could affect sh:not if (connectionsGroup.getStats().hasAdded()) { PlanNode addedTypes = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, RDF.TYPE, - clazz, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + clazz, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), + null); addedTypes = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -254,7 +256,7 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // removed type statements that match clazz could affect sh:or if (connectionsGroup.getStats().hasRemoved()) { PlanNode deletedTypes = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, RDF.TYPE, clazz, - dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), null); deletedTypes = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) @@ -271,7 +273,7 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] // added type statements that match clazz could affect sh:not if (connectionsGroup.getStats().hasAdded()) { PlanNode addedTypes = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, RDF.TYPE, clazz, - dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape)); + dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(Scope.nodeShape), null); addedTypes = getTargetChain() .getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java index ef21280a353..a4956cfa10e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java @@ -14,6 +14,7 @@ import static org.eclipse.rdf4j.model.util.Values.literal; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -23,44 +24,87 @@ import org.eclipse.rdf4j.model.Literal; import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.vocabulary.SHACL; +import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.CanProduceValidationReport; import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists; +import org.eclipse.rdf4j.sail.shacl.ast.Shape; +import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; +import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.paths.Path; +import org.eclipse.rdf4j.sail.shacl.ast.paths.SimplePath; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BulkedExternalInnerJoin; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ExternalFilterByQuery; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.NotValuesIn; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ReduceTargets; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ShiftToPropertyShape; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.TrimToTarget; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnBufferedPlanNode; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnionNode; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Unique; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnorderedSelect; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple; +import org.eclipse.rdf4j.sail.shacl.ast.targets.EffectiveTarget; +import org.eclipse.rdf4j.sail.shacl.results.ValidationResult; +import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup; import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource; -public class ClosedConstraintComponent extends AbstractConstraintComponent { +public class ClosedConstraintComponent extends AbstractConstraintComponent implements CanProduceValidationReport { - private final List paths; + private final List paths; private final List ignoredProperties; + private final Resource ignoredPropertiesHead; + private final HashSet allAllowedPredicates; + private final Shape shape; + public boolean produceValidationReports; - public ClosedConstraintComponent(ShapeSource shapeSource, List property, Resource ignoredProperties) { + public ClosedConstraintComponent(ShapeSource shapeSource, List property, Resource ignoredPropertiesHead, + Shape shape) { paths = property.stream().flatMap(r -> { return shapeSource.getObjects(r, ShapeSource.Predicates.PATH) .map(o -> ((Resource) o)) - .map(path -> Path.buildPath(shapeSource, path)); + .map(path -> Path.buildPath(shapeSource, path)) + .filter(p -> p instanceof SimplePath) + .map(p -> ((IRI) p.getId())); }).collect(Collectors.toList()); - if (ignoredProperties != null) { - this.ignoredProperties = ShaclAstLists.toList(shapeSource, ignoredProperties, IRI.class); + if (ignoredPropertiesHead != null) { + this.ignoredPropertiesHead = ignoredPropertiesHead; + this.ignoredProperties = ShaclAstLists.toList(shapeSource, ignoredPropertiesHead, IRI.class); } else { this.ignoredProperties = Collections.emptyList(); + this.ignoredPropertiesHead = null; } - + HashSet allAllowedPredicates = new HashSet<>(paths); + allAllowedPredicates.addAll(ignoredProperties); + this.allAllowedPredicates = allAllowedPredicates; + this.shape = shape; } public ClosedConstraintComponent(ClosedConstraintComponent closedConstraintComponent) { paths = closedConstraintComponent.paths; ignoredProperties = closedConstraintComponent.ignoredProperties; + ignoredPropertiesHead = closedConstraintComponent.ignoredPropertiesHead; + allAllowedPredicates = closedConstraintComponent.allAllowedPredicates; + shape = closedConstraintComponent.shape; + produceValidationReports = closedConstraintComponent.produceValidationReports; } @Override public void toModel(Resource subject, IRI predicate, Model model, Set cycleDetection) { + if (!ignoredProperties.isEmpty() && !model.contains(getId(), SHACL.IGNORED_PROPERTIES, null)) { + model.add(subject, SHACL.IGNORED_PROPERTIES, ignoredPropertiesHead); + ShaclAstLists.listToRdf(ignoredProperties, ignoredPropertiesHead, model); + } model.add(subject, SHACL.CLOSED, literal(true)); - // TODO: add ignored properties to model! - } @Override @@ -68,6 +112,216 @@ public SourceConstraintComponent getConstraintComponent() { return SourceConstraintComponent.ClosedConstraintComponent; } + @Override + public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connectionsGroup, + ValidationSettings validationSettings, PlanNodeProvider overrideTargetNode, Scope scope) { + + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider(); + + EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider); + + if (scope == Scope.propertyShape) { + Path path = getTargetChain().getPath().get(); + + PlanNode addedTargets; + + if (overrideTargetNode != null) { + addedTargets = effectiveTarget.extend(overrideTargetNode.getPlanNode(), connectionsGroup, + validationSettings.getDataGraph(), scope, + EffectiveTarget.Extend.right, + false, null); + } else { + + BufferedSplitter addedTargetsBufferedSplitter = new BufferedSplitter( + effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(), scope, false, + null)); + addedTargets = addedTargetsBufferedSplitter.getPlanNode(); + PlanNode addedByPath = path.getAllAdded(connectionsGroup, validationSettings.getDataGraph(), null); + + addedByPath = effectiveTarget.getTargetFilter(connectionsGroup, + validationSettings.getDataGraph(), Unique.getInstance(new TrimToTarget(addedByPath), false)); + + addedByPath = new ReduceTargets(addedByPath, addedTargetsBufferedSplitter.getPlanNode()); + + addedByPath = effectiveTarget.extend(addedByPath, connectionsGroup, validationSettings.getDataGraph(), + scope, + EffectiveTarget.Extend.left, false, + null); + + PlanNode addedByValue = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, + null, validationSettings.getDataGraph(), + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> { + return !allAllowedPredicates.contains(statement.getPredicate()); + })); + + addedByValue = getTargetChain() + .getEffectiveTarget(Scope.nodeShape, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) + .extend(addedByValue, connectionsGroup, validationSettings.getDataGraph(), Scope.nodeShape, + EffectiveTarget.Extend.left, + false, null); + + addedByValue = getTargetChain() + .getEffectiveTarget(Scope.nodeShape, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) + .getTargetFilter(connectionsGroup, validationSettings.getDataGraph(), addedByValue); + + addedTargets = UnionNode.getInstance(addedTargets, + new TrimToTarget(new ShiftToPropertyShape(addedByValue))); + + addedTargets = UnionNode.getInstance(addedByPath, addedTargets); + addedTargets = Unique.getInstance(addedTargets, false); + + } + + PlanNode falseNode = new BulkedExternalInnerJoin( + addedTargets, + connectionsGroup.getBaseConnection(), + validationSettings.getDataGraph(), + path.getTargetQueryFragment(new StatementMatcher.Variable("a"), new StatementMatcher.Variable("c"), + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of()), + false, + null, + BulkedExternalInnerJoin.getMapper("a", "c", scope, validationSettings.getDataGraph()) + ); + + StatementMatcher.Variable subjectVariable = stableRandomVariableProvider.next(); + StatementMatcher.Variable predicateVariable = stableRandomVariableProvider.next(); + StatementMatcher.Variable objectVariable = stableRandomVariableProvider.next(); + + SparqlFragment bgp = SparqlFragment.bgp(List.of(), + subjectVariable.asSparqlVariable() + " " + predicateVariable.asSparqlVariable() + " " + + objectVariable.asSparqlVariable() + ".", + List.of()); + String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( " + + allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", ")) + + " ) )"; + SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of()); + SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter)); + + PlanNode falseNode1 = new ExternalFilterByQuery(connectionsGroup.getBaseConnection(), + validationSettings.getDataGraph(), + falseNode, + sparqlFragment, + subjectVariable, + ValidationTuple::getValue, + (ValidationTuple validationTuple, BindingSet b) -> { + if (produceValidationReports) { + return validationTuple.addValidationResult(t -> { + ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), + b.getValue(objectVariable.getName()), + shape, + this, shape.getSeverity(), + ConstraintComponent.Scope.nodeShape, t.getContexts(), + shape.getContexts()); + validationResult.setPathIri(b.getValue(predicateVariable.getName())); + return validationResult; + + }); + } + + return validationTuple; + + }) + .getTrueNode(UnBufferedPlanNode.class); + + return falseNode1; + + } else { + + PlanNode targetNodePlanNode; + + if (overrideTargetNode != null) { + targetNodePlanNode = overrideTargetNode.getPlanNode(); + } else { + PlanNode addedTargets = effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(), + scope, false, null); + + // get all subjects of all triples where the predicate is not in the allAllowedPredicates set + UnorderedSelect unorderedSelect = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, + null, validationSettings.getDataGraph(), + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> { + return !allAllowedPredicates.contains(statement.getPredicate()); + })); + + // then remove any that are in the addedTargets node + NotValuesIn notValuesIn = new NotValuesIn(unorderedSelect, addedTargets); + + // trim to target and remove duplicates + TrimToTarget trimToTarget = new TrimToTarget(notValuesIn); + PlanNode unique = Unique.getInstance(trimToTarget, false); + + // then check that the rest are actually targets + PlanNode targetFilter = effectiveTarget.getTargetFilter(connectionsGroup, + validationSettings.getDataGraph(), + unique); + + // this should now be targets that are not valid + PlanNode extend = effectiveTarget.extend(targetFilter, connectionsGroup, + validationSettings.getDataGraph(), + scope, EffectiveTarget.Extend.left, false, null); + + targetNodePlanNode = UnionNode.getInstance(extend, effectiveTarget.getPlanNode(connectionsGroup, + validationSettings.getDataGraph(), scope, false, null)); + } + + StatementMatcher.Variable predicateVariable = stableRandomVariableProvider.next(); + + SparqlFragment bgp = SparqlFragment.bgp(List.of(), "?a " + predicateVariable.asSparqlVariable() + " ?c.", + List.of()); + String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( " + + allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", ")) + + " ) )"; + SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of()); + SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter)); + + BulkedExternalInnerJoin bulkedExternalInnerJoin = new BulkedExternalInnerJoin( + Unique.getInstance(new TrimToTarget(targetNodePlanNode), false), + connectionsGroup.getBaseConnection(), + validationSettings.getDataGraph(), + sparqlFragment, + false, + null, + (b) -> { + + ValidationTuple validationTuple = new ValidationTuple(b.getValue("a"), b.getValue("c"), + Scope.propertyShape, true, validationSettings.getDataGraph()); + + if (produceValidationReports) { + validationTuple = validationTuple.addValidationResult(t -> { + ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), + t.getValue(), + shape, + this, shape.getSeverity(), + ConstraintComponent.Scope.nodeShape, t.getContexts(), + shape.getContexts()); + validationResult.setPathIri(b.getValue(predicateVariable.getName())); + return validationResult; + + }); + } + return validationTuple; + } + ); + + return bulkedExternalInnerJoin; + } + + } + + @Override + public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] dataGraph, Scope scope, + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph, + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + return true; + } + @Override public ConstraintComponent deepClone() { return new ClosedConstraintComponent(this); @@ -101,4 +355,14 @@ public int hashCode() { result = 31 * result + (ignoredProperties != null ? ignoredProperties.hashCode() : 0); return result; } + + @Override + public void setProducesValidationReport(boolean producesValidationReport) { + this.produceValidationReports = producesValidationReport; + } + + @Override + public boolean producesValidationReport() { + return produceValidationReports; + } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java index 85fa08d8709..15a153cfc6c 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ConstraintComponent.java @@ -73,8 +73,6 @@ SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable subject, List getDefaultMessage(); - boolean overrideValidationReport(); - enum Scope { none, nodeShape, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java index fda7e0a1e35..ba9d6b2e03c 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/DisjointConstraintComponent.java @@ -48,7 +48,7 @@ PlanNode getPairwiseCheck(ConnectionsGroup connectionsGroup, ValidationSettings SparqlFragment targetQueryFragment) { return new CheckDisjointValuesBasedOnPathAndPredicate(connectionsGroup.getBaseConnection(), validationSettings.getDataGraph(), allTargets, predicate, subject, object, targetQueryFragment, shape, - this); + this, producesValidationReport); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java index 893c8de370c..bf7ffedb77e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/EqualsConstraintComponent.java @@ -48,7 +48,7 @@ CheckEqualsValuesBasedOnPathAndPredicate getPairwiseCheck(ConnectionsGroup conne StatementMatcher.Variable object, SparqlFragment targetQueryFragment) { return new CheckEqualsValuesBasedOnPathAndPredicate(connectionsGroup.getBaseConnection(), validationSettings.getDataGraph(), allTargets, predicate, subject, object, targetQueryFragment, shape, - this); + this, producesValidationReport); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java index 287982ea1a3..9dc72f88ced 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanConstraintComponent.java @@ -48,7 +48,7 @@ PlanNode getPairwiseCheck(ConnectionsGroup connectionsGroup, ValidationSettings SparqlFragment targetQueryFragment) { return new CheckLessThanValuesBasedOnPathAndPredicate(connectionsGroup.getBaseConnection(), validationSettings.getDataGraph(), allTargets, predicate, subject, object, targetQueryFragment, shape, - this); + this, producesValidationReport); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java index 84be3c440ac..8b5e39cfecc 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/LessThanOrEqualsConstraintComponent.java @@ -48,7 +48,7 @@ PlanNode getPairwiseCheck(ConnectionsGroup connectionsGroup, ValidationSettings SparqlFragment targetQueryFragment) { return new CheckLessThanOrEqualValuesBasedOnPathAndPredicate(connectionsGroup.getBaseConnection(), validationSettings.getDataGraph(), allTargets, predicate, subject, object, targetQueryFragment, shape, - this); + this, producesValidationReport); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java index f38922feb53..3d60e6f98a6 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/SparqlConstraintComponent.java @@ -29,6 +29,7 @@ import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.CanProduceValidationReport; import org.eclipse.rdf4j.sail.shacl.ast.ShaclPrefixParser; import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; @@ -46,7 +47,7 @@ import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup; import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource; -public class SparqlConstraintComponent extends AbstractConstraintComponent { +public class SparqlConstraintComponent extends AbstractConstraintComponent implements CanProduceValidationReport { private final Shape shape; public boolean produceValidationReports; @@ -168,8 +169,8 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections allTargets = effectiveTarget.getAllTargets(connectionsGroup, validationSettings.getDataGraph(), scope); } - return new SparqlConstraintSelect(connectionsGroup.getBaseConnection(), - allTargets, select, scope, validationSettings.getDataGraph(), produceValidationReports, this, shape); + return new SparqlConstraintSelect(connectionsGroup.getBaseConnection(), allTargets, select, scope, + validationSettings.getDataGraph(), produceValidationReports, this, shape); } @@ -252,7 +253,6 @@ public ConstraintComponent deepClone() { public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connectionsGroup, ValidationSettings validationSettings, boolean negatePlan, boolean negateChildren, Scope scope) { return null; - } @Override @@ -300,4 +300,15 @@ public int hashCode() { result = 31 * result + (deactivated != null ? deactivated.hashCode() : 0); return result + "SparqlConstraintComponent".hashCode(); } + + @Override + public void setProducesValidationReport(boolean producesValidationReport) { + this.produceValidationReports = producesValidationReport; + } + + @Override + public boolean producesValidationReport() { + return produceValidationReports; + } + } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java new file mode 100644 index 00000000000..f2bdcc3d1e6 --- /dev/null +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/VoidConstraintComponent.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2020 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ + +package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents; + +import java.util.List; +import java.util.Set; + +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent; +import org.eclipse.rdf4j.sail.shacl.ValidationSettings; +import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; +import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; +import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider; +import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup; + +/** + * The AST (Abstract Syntax Tree) node that is used when a sh:PropertyShape has no constraints. + */ +public class VoidConstraintComponent extends AbstractConstraintComponent { + + public VoidConstraintComponent() { + + } + + @Override + public void toModel(Resource subject, IRI predicate, Model model, Set cycleDetection) { + } + + @Override + public SourceConstraintComponent getConstraintComponent() { + return null; + } + + @Override + public ConstraintComponent deepClone() { + return new VoidConstraintComponent(); + } + + @Override + public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connectionsGroup, + ValidationSettings validationSettings, + PlanNodeProvider overrideTargetNode, Scope scope) { + return EmptyNode.getInstance(); + } + + @Override + public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] dataGraph, Scope scope, + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + return EmptyNode.getInstance(); + } + + @Override + public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph, + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + return false; + } + + @Override + public ValidationApproach getOptimalBulkValidationApproach() { + return ValidationApproach.Transactional; + } + + @Override + public List getDefaultMessage() { + return List.of(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/Path.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/Path.java index 470039ca7c9..988e8bd7853 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/Path.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/Path.java @@ -88,7 +88,7 @@ public abstract PlanNode getAllAdded(ConnectionsGroup connectionsGroup, Resource[] dataGraph, PlanNodeWrapper planNodeWrapper); /** - * Get values added in this transaction. Validation performance may improve if more value are retrieved. Validation + * Get values added in this transaction. Validation performance may improve if more values are retrieved. Validation * correctness and completeness will not be affected if fewer values are retrieved, or if no items are retrieved. */ public abstract PlanNode getAnyAdded(ConnectionsGroup connectionsGroup, diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/SimplePath.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/SimplePath.java index f797453e873..ce143ab05e0 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/SimplePath.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/paths/SimplePath.java @@ -49,7 +49,7 @@ public Resource getId() { public PlanNode getAllAdded(ConnectionsGroup connectionsGroup, Resource[] dataGraph, PlanNodeWrapper planNodeWrapper) { PlanNode unorderedSelect = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, predicate, null, - dataGraph, UnorderedSelect.Mapper.SubjectObjectPropertyShapeMapper.getFunction()); + dataGraph, UnorderedSelect.Mapper.SubjectObjectPropertyShapeMapper.getFunction(), null); if (planNodeWrapper != null) { unorderedSelect = planNodeWrapper.apply(unorderedSelect); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractPairwisePlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractPairwisePlanNode.java index a377eea305e..75be9b1778d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractPairwisePlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractPairwisePlanNode.java @@ -53,10 +53,12 @@ abstract class AbstractPairwisePlanNode implements PlanNode { private final Shape shape; private final ConstraintComponent constraintComponent; private ValidationExecutionLogger validationExecutionLogger; + private boolean produceValidationReports; public AbstractPairwisePlanNode(SailConnection connection, Resource[] dataGraph, PlanNode parent, IRI predicate, StatementMatcher.Variable subject, StatementMatcher.Variable object, - SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent) { + SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent, + boolean produceValidationReports) { this.parent = parent; this.connection = connection; assert this.connection != null; @@ -72,6 +74,7 @@ public AbstractPairwisePlanNode(SailConnection connection, Resource[] dataGraph, this.dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph); this.shape = shape; this.constraintComponent = constraintComponent; + this.produceValidationReports = produceValidationReports; } @@ -140,10 +143,16 @@ private void populateNextIterator() { } else { path = ((PropertyShape) shape).getPath(); } - return validationTuple.addValidationResult(t -> new ValidationResult( - t.getActiveTarget(), t.getValue(), shape, - constraintComponent, shape.getSeverity(), t.getScope(), t.getContexts(), - shape.getContexts(), path)); + if (produceValidationReports) { + return validationTuple.addValidationResult(t -> new ValidationResult( + t.getActiveTarget(), t.getValue(), shape, + constraintComponent, shape.getSeverity(), t.getScope(), + t.getContexts(), + shape.getContexts(), path)); + } else { + return validationTuple; + } + } else { ValidationTuple validationTuple = next.shiftToPropertyShapeScope(value); Path path; @@ -152,10 +161,15 @@ private void populateNextIterator() { } else { path = null; } - return validationTuple.addValidationResult(t -> new ValidationResult( - t.getActiveTarget(), t.getValue(), shape, - constraintComponent, shape.getSeverity(), t.getScope(), t.getContexts(), - shape.getContexts(), path)); + if (produceValidationReports) { + return validationTuple.addValidationResult(t -> new ValidationResult( + t.getActiveTarget(), t.getValue(), shape, + constraintComponent, shape.getSeverity(), t.getScope(), + t.getContexts(), + shape.getContexts(), path)); + } else { + return validationTuple; + } } }) .iterator(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java index 08cb52484d3..822703f9a71 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java @@ -58,7 +58,7 @@ public BulkedExternalInnerJoin(PlanNode leftNode, SailConnection connection, Res SparqlFragment query, boolean skipBasedOnPreviousConnection, SailConnection previousStateConnection, Function mapper) { - + super(); assert !skipBasedOnPreviousConnection || previousStateConnection != null; this.leftNode = PlanNodeHelper.handleSorting(this, leftNode); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java index 13daa2c8ab0..3035de45625 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java @@ -45,6 +45,7 @@ public class BulkedExternalLeftOuterJoin extends AbstractBulkJoinPlanNode { public BulkedExternalLeftOuterJoin(PlanNode leftNode, SailConnection connection, Resource[] dataGraph, SparqlFragment query, Function mapper) { + super(); leftNode = PlanNodeHelper.handleSorting(this, leftNode); this.leftNode = leftNode; this.query = query.getNamespacesForSparql() diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckDisjointValuesBasedOnPathAndPredicate.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckDisjointValuesBasedOnPathAndPredicate.java index fe6dce073ff..62e27698b31 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckDisjointValuesBasedOnPathAndPredicate.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckDisjointValuesBasedOnPathAndPredicate.java @@ -28,9 +28,10 @@ public class CheckDisjointValuesBasedOnPathAndPredicate extends AbstractPairwise public CheckDisjointValuesBasedOnPathAndPredicate(SailConnection connection, Resource[] dataGraph, PlanNode parent, IRI predicate, StatementMatcher.Variable subject, StatementMatcher.Variable object, - SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent) { + SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent, + boolean produceValidationReports) { super(connection, dataGraph, parent, predicate, subject, object, targetQueryFragment, shape, - constraintComponent); + constraintComponent, produceValidationReports); } Set getInvalidValues(Set valuesByPath, Set valuesByPredicate) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckEqualsValuesBasedOnPathAndPredicate.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckEqualsValuesBasedOnPathAndPredicate.java index 49fd24efd08..a950b42fcdf 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckEqualsValuesBasedOnPathAndPredicate.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckEqualsValuesBasedOnPathAndPredicate.java @@ -35,9 +35,10 @@ public class CheckEqualsValuesBasedOnPathAndPredicate extends AbstractPairwisePl public CheckEqualsValuesBasedOnPathAndPredicate(SailConnection connection, Resource[] dataGraph, PlanNode parent, IRI predicate, StatementMatcher.Variable subject, StatementMatcher.Variable object, - SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent) { + SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent, + boolean produceValidationReports) { super(connection, dataGraph, parent, predicate, subject, object, targetQueryFragment, shape, - constraintComponent); + constraintComponent, produceValidationReports); } Set getInvalidValues(Set valuesByPath, Set valuesByPredicate) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanOrEqualValuesBasedOnPathAndPredicate.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanOrEqualValuesBasedOnPathAndPredicate.java index b9d9d8e5eac..78fff34aaac 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanOrEqualValuesBasedOnPathAndPredicate.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanOrEqualValuesBasedOnPathAndPredicate.java @@ -30,9 +30,9 @@ public class CheckLessThanOrEqualValuesBasedOnPathAndPredicate extends AbstractP public CheckLessThanOrEqualValuesBasedOnPathAndPredicate(SailConnection connection, Resource[] dataGraph, PlanNode parent, IRI predicate, StatementMatcher.Variable subject, StatementMatcher.Variable object, SparqlFragment targetQueryFragment, Shape shape, - ConstraintComponent constraintComponent) { + ConstraintComponent constraintComponent, boolean produceValidationReports) { super(connection, dataGraph, parent, predicate, subject, object, targetQueryFragment, shape, - constraintComponent); + constraintComponent, produceValidationReports); } Set getInvalidValues(Set valuesByPath, Set valuesByPredicate) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanValuesBasedOnPathAndPredicate.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanValuesBasedOnPathAndPredicate.java index 41f42559618..c02ed9f60de 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanValuesBasedOnPathAndPredicate.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/CheckLessThanValuesBasedOnPathAndPredicate.java @@ -32,9 +32,10 @@ public class CheckLessThanValuesBasedOnPathAndPredicate extends AbstractPairwise public CheckLessThanValuesBasedOnPathAndPredicate(SailConnection connection, Resource[] dataGraph, PlanNode parent, IRI predicate, StatementMatcher.Variable subject, StatementMatcher.Variable object, - SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent) { + SparqlFragment targetQueryFragment, Shape shape, ConstraintComponent constraintComponent, + boolean produceValidationReports) { super(connection, dataGraph, parent, predicate, subject, object, targetQueryFragment, shape, - constraintComponent); + constraintComponent, produceValidationReports); } Set getInvalidValues(Set valuesByPath, Set valuesByPredicate) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/DatatypeFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/DatatypeFilter.java index 4d5e99f7e25..47192d3c5d9 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/DatatypeFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/DatatypeFilter.java @@ -35,12 +35,12 @@ public DatatypeFilter(PlanNode parent, IRI datatype) { } @Override - boolean checkTuple(ValidationTuple t) { - if (!(t.getValue().isLiteral())) { + boolean checkTuple(Reference t) { + if (!(t.get().getValue().isLiteral())) { return false; } - Literal literal = (Literal) t.getValue(); + Literal literal = (Literal) t.get().getValue(); if (xsdDatatype != null) { if (literal.getCoreDatatype() == xsdDatatype) { return XMLDatatypeUtil.isValidValue(literal.stringValue(), xsdDatatype); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java index a31d8ad3d94..19bd9753394 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ExternalFilterByQuery.java @@ -12,10 +12,12 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; import java.util.Objects; +import java.util.function.BiFunction; import java.util.function.Function; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.Dataset; import org.eclipse.rdf4j.query.MalformedQueryException; import org.eclipse.rdf4j.query.algebra.TupleExpr; @@ -40,40 +42,61 @@ public class ExternalFilterByQuery extends FilterPlanNode { private final StatementMatcher.Variable queryVariable; private final Function filterOn; private final String queryString; + private final BiFunction map; public ExternalFilterByQuery(SailConnection connection, Resource[] dataGraph, PlanNode parent, SparqlFragment queryFragment, StatementMatcher.Variable queryVariable, - Function filterOn) { + Function filterOn, BiFunction map) { super(parent); this.connection = connection; assert this.connection != null; this.queryVariable = queryVariable; this.filterOn = filterOn; - this.queryString = queryFragment.getNamespacesForSparql() - + StatementMatcher.StableRandomVariableProvider.normalize("SELECT " + queryVariable.asSparqlVariable() - + " WHERE {\n" + queryFragment.getFragment() + "\n}"); + if (map != null) { + this.queryString = queryFragment.getNamespacesForSparql() + + StatementMatcher.StableRandomVariableProvider.normalize("SELECT * " + + " WHERE {\n" + queryFragment.getFragment() + "\n}"); + + } else { + this.queryString = queryFragment.getNamespacesForSparql() + + StatementMatcher.StableRandomVariableProvider + .normalize("SELECT " + queryVariable.asSparqlVariable() + + " WHERE {\n" + queryFragment.getFragment() + "\n}"); + } + try { this.query = SparqlQueryParserCache.get(queryString); } catch (MalformedQueryException e) { logger.error("Malformed query:\n{}", queryString); throw e; } + dataset = PlanNodeHelper.asDefaultGraphDataset(dataGraph); + this.map = map; } @Override - boolean checkTuple(ValidationTuple t) { + boolean checkTuple(Reference t) { - Value value = filterOn.apply(t); + Value value = filterOn.apply(t.get()); SingletonBindingSet bindings = new SingletonBindingSet(queryVariable.getName(), value); try (var bindingSet = connection.evaluate(query, dataset, bindings, false)) { - return bindingSet.hasNext(); + if (bindingSet.hasNext()) { + if (map != null) { + do { + t.set(map.apply(t.get(), bindingSet.next())); + } while (bindingSet.hasNext()); + } + return true; + } } + return false; + } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java index 72b8680a99a..f11004fa25d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java @@ -35,7 +35,7 @@ public abstract class FilterPlanNode implements MultiStreamPlanNode, PlanNode { private ValidationExecutionLogger validationExecutionLogger; private boolean closed; - abstract boolean checkTuple(ValidationTuple t); + abstract boolean checkTuple(Reference t); public FilterPlanNode(PlanNode parent) { this.parent = PlanNodeHelper.handleSorting(this, parent); @@ -93,31 +93,33 @@ private void calculateNext() { } while (parentIterator.hasNext() && next == null) { - ValidationTuple temp = parentIterator.next(); + Reference reference = Reference.of(parentIterator.next()); - if (checkTuple(temp)) { + if (checkTuple(reference)) { if (trueNode != null) { - trueNode.push(temp); + trueNode.push(reference.get()); } else { if (validationExecutionLogger.isEnabled()) { validationExecutionLogger.log(FilterPlanNode.this.depth(), - FilterPlanNode.this.getClass().getSimpleName() + ":IgnoredAsTrue.next()", temp, + FilterPlanNode.this.getClass().getSimpleName() + ":IgnoredAsTrue.next()", + reference.get(), FilterPlanNode.this, getId(), null); } } } else { if (falseNode != null) { - falseNode.push(temp); + falseNode.push(reference.get()); } else { if (validationExecutionLogger.isEnabled()) { validationExecutionLogger.log(FilterPlanNode.this.depth(), - FilterPlanNode.this.getClass().getSimpleName() + ":IgnoredAsFalse.next()", temp, + FilterPlanNode.this.getClass().getSimpleName() + ":IgnoredAsFalse.next()", + reference.get(), FilterPlanNode.this, getId(), null); } } } - next = temp; + next = reference.get(); } @@ -259,4 +261,24 @@ public int hashCode() { return Objects.hash(parent); } + static class Reference { + private ValidationTuple t; + + private Reference(ValidationTuple t) { + this.t = t; + } + + public static Reference of(ValidationTuple t) { + return new Reference(t); + } + + public ValidationTuple get() { + return t; + } + + public void set(ValidationTuple t) { + this.t = t; + } + } + } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsObject.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsObject.java index 6ccd84689fc..61961b42de5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsObject.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsObject.java @@ -35,8 +35,8 @@ public FilterTargetIsObject(SailConnection connection, Resource[] dataGraph, Pla } @Override - boolean checkTuple(ValidationTuple t) { - Value target = t.getActiveTarget(); + boolean checkTuple(Reference t) { + Value target = t.get().getActiveTarget(); return connection.hasStatement(null, null, target, true, dataGraph); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsSubject.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsSubject.java index b1ea9297054..7ba6fdaef0a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsSubject.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterTargetIsSubject.java @@ -35,9 +35,9 @@ public FilterTargetIsSubject(SailConnection connection, Resource[] dataGraph, Pl } @Override - boolean checkTuple(ValidationTuple t) { + boolean checkTuple(Reference t) { - Value target = t.getActiveTarget(); + Value target = t.get().getActiveTarget(); if (target.isResource()) { return connection.hasStatement((Resource) target, null, null, true, dataGraph); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LanguageInFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LanguageInFilter.java index a98d848286f..ac2efb2d879 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LanguageInFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LanguageInFilter.java @@ -35,12 +35,12 @@ public LanguageInFilter(PlanNode parent, Set lowerCaseLanguageIn, List language = ((Literal) t.getValue()).getLanguage(); + Optional language = ((Literal) t.get().getValue()).getLanguage(); if (!language.isPresent()) { return false; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LiteralComparatorFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LiteralComparatorFilter.java index 6b1ba2b40aa..a59e41f1719 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LiteralComparatorFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LiteralComparatorFilter.java @@ -35,8 +35,8 @@ public LiteralComparatorFilter(PlanNode parent, Literal compareTo, Compare.Compa } @Override - boolean checkTuple(ValidationTuple t) { - Value literal = t.getValue(); + boolean checkTuple(Reference t) { + Value literal = t.get().getValue(); return QueryEvaluationUtility.compare(compareTo, literal, this.compareOp).orElse(false); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MaxLengthFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MaxLengthFilter.java index 0f0b6b79146..c2ef2b5482d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MaxLengthFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MaxLengthFilter.java @@ -28,8 +28,8 @@ public MaxLengthFilter(PlanNode parent, long maxLength) { } @Override - boolean checkTuple(ValidationTuple t) { - Value literal = t.getValue(); + boolean checkTuple(Reference t) { + Value literal = t.get().getValue(); return literal.stringValue().length() <= maxLength; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MinLengthFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MinLengthFilter.java index 26934d2fa26..a8a673cc852 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MinLengthFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/MinLengthFilter.java @@ -28,8 +28,8 @@ public MinLengthFilter(PlanNode parent, long minLength) { } @Override - boolean checkTuple(ValidationTuple t) { - Value literal = t.getValue(); + boolean checkTuple(Reference t) { + Value literal = t.get().getValue(); return literal.stringValue().length() >= minLength; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/NodeKindFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/NodeKindFilter.java index b2ebeb6f646..9190ea83c6a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/NodeKindFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/NodeKindFilter.java @@ -29,9 +29,9 @@ public NodeKindFilter(PlanNode parent, NodeKindConstraintComponent.NodeKind node } @Override - boolean checkTuple(ValidationTuple t) { + boolean checkTuple(Reference t) { - Value value = t.getValue(); + Value value = t.get().getValue(); /* * BlankNode(SHACL.BLANK_NODE), IRI(SHACL.IRI), Literal(SHACL.LITERAL), BlankNodeOrIRI(SHACL.BLANK_NODE_OR_IRI), * BlankNodeOrLiteral(SHACL.BLANK_NODE_OR_LITERAL), IRIOrLiteral(SHACL.IRI_OR_LITERAL), diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PatternFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PatternFilter.java index e6d0f0bc9fe..be9b2abe695 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PatternFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PatternFilter.java @@ -67,8 +67,8 @@ public PatternFilter(PlanNode parent, String pattern, String flags) { } @Override - boolean checkTuple(ValidationTuple t) { - Value literal = t.getValue(); + boolean checkTuple(Reference t) { + Value literal = t.get().getValue(); return pattern.matcher(literal.stringValue()).matches(); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/SparqlConstraintSelect.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/SparqlConstraintSelect.java index 4da0c169c14..0d8dacf53ed 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/SparqlConstraintSelect.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/SparqlConstraintSelect.java @@ -124,7 +124,7 @@ private void calculateNext() { Value value = bindingSet.getValue("value"); Value path = bindingSet.getValue("path"); - if (scope == ConstraintComponent.Scope.nodeShape && produceValidationReports) { + if (scope == ConstraintComponent.Scope.nodeShape) { next = nextTarget.addValidationResult(t -> { ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), value, shape, @@ -137,7 +137,15 @@ private void calculateNext() { } else { ValidationTuple validationTuple = new ValidationTuple(nextTarget.getActiveTarget(), value, scope, true, nextTarget.getContexts()); - next = ValidationTupleHelper.join(nextTarget, validationTuple); + next = ValidationTupleHelper.join(nextTarget, validationTuple).addValidationResult(t -> { + ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), value, + shape, + constraintComponent, shape.getSeverity(), + ConstraintComponent.Scope.propertyShape, t.getContexts(), + shape.getContexts()); + validationResult.setPathIri(path); + return validationResult; + }); } } else { results.close(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnorderedSelect.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnorderedSelect.java index bfdb996fcd4..78bc577697f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnorderedSelect.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnorderedSelect.java @@ -14,9 +14,11 @@ import java.util.Arrays; import java.util.Objects; import java.util.function.BiFunction; +import java.util.function.Function; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.common.iteration.FilterIteration; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; @@ -41,12 +43,14 @@ public class UnorderedSelect implements PlanNode { private final Value object; private final Resource[] dataGraph; private final BiFunction mapper; + private final Function filter; private boolean printed = false; private ValidationExecutionLogger validationExecutionLogger; public UnorderedSelect(SailConnection connection, Resource subject, IRI predicate, Value object, - Resource[] dataGraph, BiFunction mapper) { + Resource[] dataGraph, BiFunction mapper, + Function filter) { this.connection = connection; assert this.connection != null; this.subject = subject; @@ -54,6 +58,7 @@ public UnorderedSelect(SailConnection connection, Resource subject, IRI predicat this.object = object; this.dataGraph = dataGraph; this.mapper = mapper; + this.filter = filter; } @Override @@ -65,7 +70,22 @@ public CloseableIteration iterator() { @Override protected void init() { assert statements == null; - statements = connection.getStatements(subject, predicate, object, true, dataGraph); + if (filter != null) { + statements = new FilterIteration( + connection.getStatements(subject, predicate, object, true, dataGraph)) { + @Override + protected boolean accept(Statement st) { + return filter.apply(st); + } + + @Override + protected void handleClose() { + + } + }; + } else { + statements = connection.getStatements(subject, predicate, object, true, dataGraph); + } } @Override @@ -162,14 +182,16 @@ public boolean equals(Object o) { Objects.equals(predicate, that.predicate) && Objects.equals(object, that.object) && Arrays.equals(dataGraph, that.dataGraph) && - mapper.equals(that.mapper); + mapper.equals(that.mapper) && + Objects.equals(filter, that.filter); } else { return Objects.equals(connection, that.connection) && Objects.equals(subject, that.subject) && Objects.equals(predicate, that.predicate) && Objects.equals(object, that.object) && Arrays.equals(dataGraph, that.dataGraph) && - mapper.equals(that.mapper); + mapper.equals(that.mapper) && + Objects.equals(filter, that.filter); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java index 8c6bebc004b..3bad814ab15 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java @@ -215,21 +215,21 @@ public Value getActiveTarget() { @Override public String toString() { try { - return "ValidationTuple{" + - "activeTarget=" + getActiveTarget() + - "value=" + getValue() + - "chain=" + Arrays.toString(chain) + - ", scope=" + scope + - ", propertyShapeScopeWithValue=" + propertyShapeScopeWithValue + - ", compressedTuples=" + Arrays.toString(compressedTuples.toArray()) + - '}'; + return "ValidationTuple(" + scope + ") [ " + Arrays.stream(chain).map(v -> { + if (v == getActiveTarget()) { + return "T–" + v; + } else if (v == getValue()) { + return "V–" + v + ""; + } + return v.stringValue(); + }).collect(Collectors.joining(" -> ")) + " }, propertyShapeScopeWithValue=" + propertyShapeScopeWithValue + + ", compressedTuples=" + Arrays.toString(compressedTuples.toArray()); + } catch (Throwable t) { - return "ValidationTuple{" + - "chain=" + Arrays.toString(chain) + - ", scope=" + scope + - ", propertyShapeScopeWithValue=" + propertyShapeScopeWithValue + - ", compressedTuples=" + Arrays.toString(compressedTuples.toArray()) + - '}'; + return "ValidationTuple(" + scope + ") { " + + Arrays.stream(chain).map(Value::stringValue).collect(Collectors.joining(" -> ")) + + " }, propertyShapeScopeWithValue=" + propertyShapeScopeWithValue + + ", compressedTuples=" + Arrays.toString(compressedTuples.toArray()); } } @@ -495,6 +495,11 @@ public ValidationTuple join(ValidationTuple right) { if (scope == ConstraintComponent.Scope.propertyShape) { validationTuple = validationTuple.setValue(right.getValue()); } + + for (ValidationResult validationResult : right.getValidationResult()) { + validationTuple = validationTuple.addValidationResult(t -> validationResult); + } + return validationTuple; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValueInFilter.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValueInFilter.java index 8f48831c1bd..b73906c7a5b 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValueInFilter.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValueInFilter.java @@ -30,8 +30,8 @@ public ValueInFilter(PlanNode parent, Set valueSet) { } @Override - boolean checkTuple(ValidationTuple t) { - return valueSet.contains(t.getValue()); + boolean checkTuple(Reference t) { + return valueSet.contains(t.get().getValue()); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllObjects.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllObjects.java index 05d88143f64..51f0634a488 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllObjects.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllObjects.java @@ -64,7 +64,7 @@ private PlanNode getAddedRemovedInner(SailConnection connection, Resource[] data ConstraintComponent.Scope scope) { return Unique.getInstance(new UnorderedSelect(connection, null, - null, null, dataGraph, UnorderedSelect.Mapper.ObjectScopedMapper.getFunction(scope)), false); + null, null, dataGraph, UnorderedSelect.Mapper.ObjectScopedMapper.getFunction(scope), null), false); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllSubjects.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllSubjects.java index 4877990cebb..b55de730338 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllSubjects.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/DashAllSubjects.java @@ -62,7 +62,7 @@ private PlanNode getAddedRemovedInner(SailConnection connection, Resource[] data ConstraintComponent.Scope scope) { return Unique.getInstance(new UnorderedSelect(connection, null, - null, null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope)), false); + null, null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), null), false); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/EffectiveTarget.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/EffectiveTarget.java index 248b1e6c51f..7a68228bc58 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/EffectiveTarget.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/EffectiveTarget.java @@ -50,6 +50,7 @@ public class EffectiveTarget { public static final String TARGET_VAR_PREFIX = "target_"; public static final String[] TARGET_NAMES = IntStream.range(0, 1000) .mapToObj(i -> TARGET_VAR_PREFIX + String.format("%010d", i)) + .map(String::intern) .toArray(String[]::new); private final ArrayDeque chain; private final EffectiveTargetFragment optional; @@ -344,7 +345,7 @@ public PlanNode getTargetFilter(ConnectionsGroup connectionsGroup, Resource[] da // TODO: this is a slow way to solve this problem! We should use bulk operations. return new ExternalFilterByQuery(connectionsGroup.getBaseConnection(), dataGraph, parent, sparqlFragment, last.var, - ValidationTuple::getActiveTarget) + ValidationTuple::getActiveTarget, null) .getTrueNode(UnBufferedPlanNode.class); } @@ -396,6 +397,10 @@ public List> getAllTargetVariables() { .collect(Collectors.toCollection(ArrayList::new)); } + public Variable getOptionalVar() { + return Objects.requireNonNull(optional, "Optional was null").var; + } + public enum Extend { left, right diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/RSXTargetShape.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/RSXTargetShape.java index 1aa16e99456..93aae734be7 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/RSXTargetShape.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/RSXTargetShape.java @@ -112,7 +112,7 @@ public PlanNode getTargetFilter(ConnectionsGroup connectionsGroup, Resource[] da // TODO: this is a slow way to solve this problem! We should use bulk operations. return new ExternalFilterByQuery(connectionsGroup.getBaseConnection(), dataGraph, parent, sparqlFragment, variable, - ValidationTuple::getActiveTarget).getTrueNode(UnBufferedPlanNode.class); + ValidationTuple::getActiveTarget, null).getTrueNode(UnBufferedPlanNode.class); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/SparqlTarget.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/SparqlTarget.java index 81b05d8f555..da22b013fdb 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/SparqlTarget.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/SparqlTarget.java @@ -114,7 +114,7 @@ public PlanNode getTargetFilter(ConnectionsGroup connectionsGroup, Resource[] da // TODO: this is a slow way to solve this problem! We should use bulk operations. return new ExternalFilterByQuery(connectionsGroup.getBaseConnection(), dataGraph, parent, sparqlFragment, StatementMatcher.Variable.THIS, - ValidationTuple::getActiveTarget).getTrueNode(UnBufferedPlanNode.class); + ValidationTuple::getActiveTarget, null).getTrueNode(UnBufferedPlanNode.class); } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetClass.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetClass.java index 4a78b90385e..3ad450e7901 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetClass.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetClass.java @@ -65,7 +65,7 @@ private PlanNode getAddedRemovedInner(SailConnection connection, Resource[] data if (targetClass.size() == 1) { Resource clazz = targetClass.stream().findAny().get(); planNode = new UnorderedSelect(connection, null, RDF.TYPE, clazz, - dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope)); + dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), null); } else { planNode = new Select(connection, SparqlFragment.bgp(Set.of(), diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetObjectsOf.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetObjectsOf.java index 98d4fa24071..3d7504ba77a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetObjectsOf.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetObjectsOf.java @@ -67,7 +67,7 @@ private PlanNode getAddedRemovedInner(SailConnection connection, Resource[] data PlanNode planNode = targetObjectsOf.stream() .map(predicate -> (PlanNode) new UnorderedSelect(connection, null, - predicate, null, dataGraph, UnorderedSelect.Mapper.ObjectScopedMapper.getFunction(scope))) + predicate, null, dataGraph, UnorderedSelect.Mapper.ObjectScopedMapper.getFunction(scope), null)) .reduce(UnionNode::getInstance) .orElse(EmptyNode.getInstance()); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetSubjectsOf.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetSubjectsOf.java index 72c59f57455..72111f319c6 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetSubjectsOf.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetSubjectsOf.java @@ -67,7 +67,8 @@ private PlanNode getAddedRemovedInner(SailConnection connection, Resource[] data PlanNode planNode = targetSubjectsOf.stream() .map(predicate -> (PlanNode) new UnorderedSelect(connection, null, - predicate, null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope))) + predicate, null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), + null)) .reduce(UnionNode::getInstance) .orElse(EmptyNode.getInstance()); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/ValidationResult.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/ValidationResult.java index a8b4854dcdd..faf328d7917 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/ValidationResult.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/ValidationResult.java @@ -158,11 +158,11 @@ public Model asModel(Model model, Set rdfListDedupe) { value.ifPresent(v -> model.add(getId(), SHACL.VALUE, v)); - if (this.path != null) { + if (pathIri != null) { + model.add(getId(), SHACL.RESULT_PATH, pathIri); + } else if (this.path != null) { path.toModel(path.getId(), null, model, new HashSet<>()); model.add(getId(), SHACL.RESULT_PATH, path.getId()); - } else if (pathIri != null) { - model.add(getId(), SHACL.RESULT_PATH, pathIri); } if (rsxPairwisePath != null) { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/LazyValidationReport.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/LazyValidationReport.java index d7eafafdccf..923ee2c9023 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/LazyValidationReport.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/LazyValidationReport.java @@ -35,7 +35,6 @@ import org.eclipse.rdf4j.rio.Rio; import org.eclipse.rdf4j.rio.WriterConfig; import org.eclipse.rdf4j.rio.helpers.BasicWriterSettings; -import org.eclipse.rdf4j.sail.shacl.ShaclSailConnection; import org.eclipse.rdf4j.sail.shacl.results.ValidationReport; import org.eclipse.rdf4j.sail.shacl.results.ValidationResult; import org.slf4j.Logger; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java index e89b1814371..495bbebbb24 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java @@ -70,12 +70,9 @@ private void calculateNext() { assert !validationResults.isEmpty(); - if (!validationResults.isEmpty()) { - ValidationResult validationResult1 = validationResults.get(validationResults.size() - 1); - validationResultsRet.add(validationResult1); + validationResultsRet.addAll(validationResults); - counter++; - } + counter++; } next = validationResultsRet.iterator(); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java index a82ec935999..222ad2ed20e 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java @@ -582,6 +582,12 @@ void referenceImplementationTestCaseValidation(TestCase testCase) { return; } + // the TopBraid SHACL API doesn't agree with other implementations on how sh:closed should work in a property + // shape + if (testCase.testCasePath.startsWith("test-cases/closed/propertyShape/")) { + return; + } + printTestCase(testCase); Dataset shaclDataset = DatasetFactory.create(); @@ -622,66 +628,62 @@ void referenceImplementationTestCaseValidation(TestCase testCase) { } - org.apache.jena.rdf.model.Resource report = ValidationUtil.validateModel(data, shacl, true); + org.apache.jena.rdf.model.Resource report = ValidationUtil.validateModel(data, shacl, false); org.apache.jena.rdf.model.Model model = report.getModel(); model.setNsPrefix("sh", "http://www.w3.org/ns/shacl#"); boolean conforms = report.getProperty(SH.conforms).getBoolean(); - if (testCase.expectedResult == ExpectedResult.valid) { - Assertions.assertTrue(conforms, "Expected test case to conform"); - } else { - Assertions.assertFalse(conforms, "Expected test case to not conform"); - - try { - Model validationReportExpected = Rio.parse(new StringReader(ModelPrinter.get().print(model)), - RDFFormat.TRIG); - - try { - - InputStream resourceAsStream = getResourceAsStream(testCase.getTestCasePath() + "report.ttl"); - Model validationReportActual = extractValidationReport(getModel(resourceAsStream)); - - validationReportExpected = extractValidationReport(validationReportExpected); + try { + InputStream resourceAsStream = getResourceAsStream(testCase.getTestCasePath() + "report.ttl"); + Model validationReportActual = extractValidationReport(getModel(resourceAsStream)); - for (Model validationReport : Arrays.asList(validationReportActual, validationReportExpected)) { - validationReport.remove(null, RDF4J.TRUNCATED, null); - validationReport.remove(null, RSX.dataGraph, null); - validationReport.remove(null, RSX.shapesGraph, null); - validationReport.remove(null, RSX.actualPairwisePath, null); + Model validationReportExpected = Rio.parse(new StringReader(ModelPrinter.get().print(model)), + RDFFormat.TRIG); - // We don't have any default values for sh:resultMessage - validationReport.remove(null, SHACL.RESULT_MESSAGE, null); + validationReportExpected = extractValidationReport(validationReportExpected); - // Remove the contents fo the SPARQL constraint since the reference implementation only seems to - // add the Resource of the SPARQL constraint. - ArrayList sparqlConstraints = Lists - .newArrayList(validationReport.getStatements(null, RDF.TYPE, SHACL.SPARQL_CONSTRAINT)); - for (Statement sparqlConstraint : sparqlConstraints) { - validationReport.remove(sparqlConstraint.getSubject(), null, null); - } + if (testCase.expectedResult == ExpectedResult.valid) { + Assertions.assertTrue(conforms, + "Expected test case to conform\n" + modelToString(validationReportExpected, RDFFormat.TURTLE)); + } else { + Assertions.assertFalse(conforms, "Expected test case to not conform\n" + + modelToString(validationReportExpected, RDFFormat.TURTLE)); + } - } + for (Model validationReport : Arrays.asList(validationReportActual, validationReportExpected)) { + validationReport.remove(null, RDF4J.TRUNCATED, null); + validationReport.remove(null, RSX.dataGraph, null); + validationReport.remove(null, RSX.shapesGraph, null); + validationReport.remove(null, RSX.actualPairwisePath, null); + + // We don't have any default values for sh:resultMessage + validationReport.remove(null, SHACL.RESULT_MESSAGE, null); + + // Remove the contents fo the SPARQL constraint since the reference implementation only seems to + // add the Resource of the SPARQL constraint. + ArrayList sparqlConstraints = Lists + .newArrayList(validationReport.getStatements(null, RDF.TYPE, SHACL.SPARQL_CONSTRAINT)); + for (Statement sparqlConstraint : sparqlConstraints) { + validationReport.remove(sparqlConstraint.getSubject(), null, null); + } - validationReportActual = new ValidationReportBnodeDuplicator(validationReportActual).getModel(); - validationReportExpected = new ValidationReportBnodeDuplicator(validationReportExpected).getModel(); + } - if (!Models.isomorphic(validationReportActual, validationReportExpected)) { + validationReportActual = new ValidationReportBnodeDuplicator(validationReportActual).getModel(); + validationReportExpected = new ValidationReportBnodeDuplicator(validationReportExpected).getModel(); - String validationReportExpectedString = modelToString(validationReportExpected, - RDFFormat.TURTLE); - String validationReportActualString = modelToString(validationReportActual, RDFFormat.TURTLE); - Assertions.assertEquals(validationReportExpectedString, validationReportActualString); - } - } catch (IOException e) { - throw new RuntimeException(e); - } + if (!Models.isomorphic(validationReportActual, validationReportExpected)) { - } catch (IOException e) { - throw new IllegalStateException(); + String validationReportExpectedString = modelToString(validationReportExpected, + RDFFormat.TURTLE); + String validationReportActualString = modelToString(validationReportActual, RDFFormat.TURTLE); + Assertions.assertEquals(validationReportExpectedString, validationReportActualString); } + } catch (IOException e) { + throw new IllegalStateException(); } } @@ -707,7 +709,7 @@ private static void checkShapesConformToW3cShaclRecommendation(org.apache.jena.r throw new IllegalStateException(e); } - org.apache.jena.rdf.model.Resource report = ValidationUtil.validateModel(shacl, w3cShacl, true); + org.apache.jena.rdf.model.Resource report = ValidationUtil.validateModel(shacl, w3cShacl, false); boolean conforms = report.getProperty(SH.conforms).getBoolean(); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/W3cComplianceTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/W3cComplianceTest.java index 840cf92c147..cd428fceae4 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/W3cComplianceTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/W3cComplianceTest.java @@ -54,8 +54,6 @@ public class W3cComplianceTest { private final static Set TESTS_FAILING_DUE_TO_MISSING_FEATURES_FROM_THE_SPEC = Set.of( - "/core/node/closed-001.ttl", - "/core/node/closed-002.ttl", "/core/node/xone-001.ttl", "/core/node/xone-duplicate.ttl", "/core/path/path-complex-001.ttl", diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/query1.rq new file mode 100644 index 00000000000..3227d77c657 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/report.ttl new file mode 100644 index 00000000000..3a0682a3c13 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case1/report.ttl @@ -0,0 +1,42 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query1.rq new file mode 100644 index 00000000000..38f97e41726 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:notAllowedProperty "This property is not allowed" ; + ex:age 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/report.ttl new file mode 100644 index 00000000000..3a0682a3c13 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case2/report.ttl @@ -0,0 +1,42 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query2.rq new file mode 100644 index 00000000000..729dc51fbe7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/query2.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:mother ex:Shelly; + ex:father ex:Hubert; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/report.ttl new file mode 100644 index 00000000000..e5706e720cc --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case3/report.ttl @@ -0,0 +1,58 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath _:52f1c699eb694f3e9ce55ac0c69fbde248; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:MaxCountConstraintComponent; + sh:sourceShape _:52f1c699eb694f3e9ce55ac0c69fbde247 + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:mother; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:Shelly + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:father; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:Hubert + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], _:52f1c699eb694f3e9ce55ac0c69fbde247, [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . + +_:52f1c699eb694f3e9ce55ac0c69fbde247 a sh:PropertyShape; + sh:maxCount 1; + sh:path _:52f1c699eb694f3e9ce55ac0c69fbde248 . + +_:52f1c699eb694f3e9ce55ac0c69fbde248 sh:alternativePath (ex:father ex:mother) . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query1.rq new file mode 100644 index 00000000000..77a55384a16 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query2.rq new file mode 100644 index 00000000000..074a12c1e36 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/query2.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/report.ttl new file mode 100644 index 00000000000..3a0682a3c13 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/invalid/case4/report.ttl @@ -0,0 +1,42 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/complex/shacl.trig new file mode 100644 index 00000000000..d122401a133 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/shacl.trig @@ -0,0 +1,37 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + sh:minCount 1 ; + sh:maxCount 1 ; + ], + [ + sh:path ex:age ; + sh:minCount 1 ; + sh:minInclusive 18 ; + ], + [ + sh:path [ sh:alternativePath ( ex:father ex:mother ) ] ; + sh:maxCount 1 ; + ], + [ + sh:path rdf:type; + ] + ; + . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query1.rq new file mode 100644 index 00000000000..2ea9878fc43 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query2.rq new file mode 100644 index 00000000000..90e36f9e538 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/complex/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/query1.rq new file mode 100644 index 00000000000..cb9e6e3d4aa --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/query1.rq @@ -0,0 +1,17 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/report.ttl new file mode 100644 index 00000000000..042b01e4955 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case1/report.ttl @@ -0,0 +1,43 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ] + ]; + sh:value "This property is not allowed" + ] . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query1.rq new file mode 100644 index 00000000000..63b1413dbc9 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:knows [ + ex:name "John" ; + ex:notAllowedProperty "This property is not allowed" ; + ex:age 30 ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/report.ttl new file mode 100644 index 00000000000..fc3a8f61fcf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case2/report.ttl @@ -0,0 +1,43 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:value "This property is not allowed" + ] . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query1.rq new file mode 100644 index 00000000000..a9bb4a9aaca --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:person2 . + + ex:person2 + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query2.rq new file mode 100644 index 00000000000..5db39127a9c --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/query2.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:person2 + ex:mother ex:Shelly; + ex:father ex:Hubert; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/report.ttl new file mode 100644 index 00000000000..71643e644a0 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case3/report.ttl @@ -0,0 +1,60 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:person2; + sh:resultPath _:9cdd6656b21b4cd488804bc6bf90f6834007; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:MaxCountConstraintComponent; + sh:sourceShape _:9cdd6656b21b4cd488804bc6bf90f6834006 + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:mother; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape _:9cdd6656b21b4cd488804bc6bf90f6833996; + sh:value ex:Shelly + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:father; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape _:9cdd6656b21b4cd488804bc6bf90f6833996; + sh:value ex:Hubert + ] . + +_:9cdd6656b21b4cd488804bc6bf90f6833996 a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], _:9cdd6656b21b4cd488804bc6bf90f6834006, [ a sh:PropertyShape; + sh:path rdf:type + ] . + +_:9cdd6656b21b4cd488804bc6bf90f6834006 a sh:PropertyShape; + sh:maxCount 1; + sh:path _:9cdd6656b21b4cd488804bc6bf90f6834007 . + +_:9cdd6656b21b4cd488804bc6bf90f6834007 sh:alternativePath (ex:father ex:mother) . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query1.rq new file mode 100644 index 00000000000..85c3bcf9a40 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query1.rq @@ -0,0 +1,17 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:person2 . + + ex:person2 + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query2.rq new file mode 100644 index 00000000000..73d338b7c0d --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/query2.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:person2 + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/report.ttl new file mode 100644 index 00000000000..fc3a8f61fcf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case4/report.ttl @@ -0,0 +1,43 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:value "This property is not allowed" + ] . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query1.rq new file mode 100644 index 00000000000..671b5278bce --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query1.rq @@ -0,0 +1,19 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . + + + ex:person2 + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query2.rq new file mode 100644 index 00000000000..70a9210be6e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:knows ex:person2 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/report.ttl new file mode 100644 index 00000000000..fc3a8f61fcf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case5/report.ttl @@ -0,0 +1,43 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:value "This property is not allowed" + ] . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query1.rq new file mode 100644 index 00000000000..330191fdceb --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query1.rq @@ -0,0 +1,20 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . + + + ex:person2 + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + ex:alsoNotAllowedProperty "This property is also not allowed" ; + . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query2.rq new file mode 100644 index 00000000000..70a9210be6e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:knows ex:person2 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/report.ttl new file mode 100644 index 00000000000..bea25faa43a --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/invalid/case6/report.ttl @@ -0,0 +1,52 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape _:48ff8592dadf417293fd24e9df9b82df1; + sh:value "This property is not allowed" + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:alsoNotAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape _:48ff8592dadf417293fd24e9df9b82df1; + sh:value "This property is also not allowed" + ] . + +_:48ff8592dadf417293fd24e9df9b82df1 a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ] . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/shacl.trig new file mode 100644 index 00000000000..85644322925 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/shacl.trig @@ -0,0 +1,40 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:property [ + sh:path [ sh:alternativePath (ex:knows ( ex:knows1 ex:knows2 ) ) ]; + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + sh:minCount 1 ; + sh:maxCount 1 ; + ], + [ + sh:path ex:age ; + sh:minCount 1 ; + sh:minInclusive 18 ; + ], + [ + sh:path [ sh:alternativePath ( ex:father ex:mother ) ] ; + sh:maxCount 1 ; + ], + [ + sh:path rdf:type; + ] + ; + + ] . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/query1.rq new file mode 100644 index 00000000000..29a351e3fc6 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query1.rq new file mode 100644 index 00000000000..0de45d1675d --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query1.rq new file mode 100644 index 00000000000..02c297f80ca --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:validPerson2 . + + ex:validPerson2 + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query2.rq new file mode 100644 index 00000000000..6773d4e93e4 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson2 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/propertyShape/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/query1.rq new file mode 100644 index 00000000000..3227d77c657 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/report.ttl new file mode 100644 index 00000000000..e6e572ffc3a --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case1/report.ttl @@ -0,0 +1,33 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query1.rq new file mode 100644 index 00000000000..38f97e41726 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:notAllowedProperty "This property is not allowed" ; + ex:age 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/report.ttl new file mode 100644 index 00000000000..e6e572ffc3a --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case2/report.ttl @@ -0,0 +1,33 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query2.rq new file mode 100644 index 00000000000..729dc51fbe7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/query2.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:mother ex:Shelly; + ex:father ex:Hubert; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/report.ttl new file mode 100644 index 00000000000..d093b057fbc --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case3/report.ttl @@ -0,0 +1,41 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:mother; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:Shelly + ], [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:father; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:Hubert + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query1.rq new file mode 100644 index 00000000000..77a55384a16 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query2.rq new file mode 100644 index 00000000000..074a12c1e36 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/query2.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/report.ttl new file mode 100644 index 00000000000..e6e572ffc3a --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/invalid/case4/report.ttl @@ -0,0 +1,33 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultPath ex:notAllowedProperty; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:ClosedConstraintComponent; + sh:sourceShape ex:Person; + sh:value "This property is not allowed" + ] . + +ex:Person a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/simple/shacl.trig new file mode 100644 index 00000000000..828284e5b0b --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/shacl.trig @@ -0,0 +1,29 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + ], + [ + sh:path ex:age ; + ], + [ + sh:path rdf:type; + ] + ; + . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query1.rq new file mode 100644 index 00000000000..2ea9878fc43 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query2.rq new file mode 100644 index 00000000000..90e36f9e538 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/simple/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/complex/mms/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/complex/mms/valid/case1/query1.rq index 48066ef6125..7ff506dbad8 100644 --- a/core/sail/shacl/src/test/resources/test-cases/complex/mms/valid/case1/query1.rq +++ b/core/sail/shacl/src/test/resources/test-cases/complex/mms/valid/case1/query1.rq @@ -5,18 +5,18 @@ PREFIX s: PREFIX vaem: INSERT DATA { - - dtype:hasMember , ; + + dtype:hasMember , ; a mms:EnumerationType ; mms:allowMultiple false . - + s:description "first value" ; - dtype:value ; + dtype:value ; vaem:name "one" . - + s:description "second value" ; - dtype:value ; + dtype:value ; vaem:name "two" . -} \ No newline at end of file +} From 2652dcb2a8da6b1234b560896d551be8292f4dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 11 Apr 2024 09:59:15 +0200 Subject: [PATCH 2/4] GH-1112 sh:closed within sh:not --- .../ClosedConstraintComponent.java | 115 +++++++++++++++++- .../rdf4j/sail/shacl/AbstractShaclTest.java | 6 + .../closed/not/invalid/case1/query1.rq | 14 +++ .../closed/not/invalid/case1/report.ttl | 34 ++++++ .../closed/not/invalid/case2/query1.rq | 12 ++ .../closed/not/invalid/case2/query2.rq | 10 ++ .../closed/not/invalid/case2/report.ttl | 34 ++++++ .../closed/not/invalid/case3/query1.rq | 14 +++ .../closed/not/invalid/case3/query2.rq | 10 ++ .../closed/not/invalid/case3/report.ttl | 34 ++++++ .../test-cases/closed/not/shacl.trig | 35 ++++++ .../closed/not/valid/case1/query1.rq | 14 +++ .../closed/not/valid/case1/report.ttl | 12 ++ .../closed/not/valid/case2/query1.rq | 14 +++ .../closed/not/valid/case2/query2.rq | 10 ++ .../closed/not/valid/case2/report.ttl | 12 ++ .../closed/not/valid/case3/query1.rq | 14 +++ .../closed/not/valid/case3/query2.rq | 10 ++ .../closed/not/valid/case3/report.ttl | 12 ++ .../notPropertyShape/invalid/case1/query1.rq | 16 +++ .../notPropertyShape/invalid/case1/report.ttl | 48 ++++++++ .../notPropertyShape/invalid/case2/query1.rq | 15 +++ .../notPropertyShape/invalid/case2/query2.rq | 10 ++ .../notPropertyShape/invalid/case2/report.ttl | 48 ++++++++ .../notPropertyShape/invalid/case3/query1.rq | 17 +++ .../notPropertyShape/invalid/case3/query2.rq | 11 ++ .../notPropertyShape/invalid/case3/report.ttl | 48 ++++++++ .../notPropertyShape/invalid/case4/query1.rq | 17 +++ .../notPropertyShape/invalid/case4/query2.rq | 10 ++ .../notPropertyShape/invalid/case4/report.ttl | 48 ++++++++ .../closed/notPropertyShape/shacl.trig | 42 +++++++ .../notPropertyShape/valid/case1/query1.rq | 16 +++ .../notPropertyShape/valid/case1/report.ttl | 12 ++ .../notPropertyShape/valid/case2/query1.rq | 16 +++ .../notPropertyShape/valid/case2/query2.rq | 10 ++ .../notPropertyShape/valid/case2/report.ttl | 12 ++ .../notPropertyShape/valid/case3/query1.rq | 17 +++ .../notPropertyShape/valid/case3/query2.rq | 10 ++ .../notPropertyShape/valid/case3/report.ttl | 12 ++ .../closed/or/invalid/case1/query1.rq | 15 +++ .../closed/or/invalid/case1/report.ttl | 44 +++++++ .../closed/or/invalid/case2/query1.rq | 13 ++ .../closed/or/invalid/case2/query2.rq | 10 ++ .../closed/or/invalid/case2/report.ttl | 44 +++++++ .../closed/or/invalid/case3/query1.rq | 13 ++ .../closed/or/invalid/case3/query2.rq | 13 ++ .../closed/or/invalid/case3/report.ttl | 44 +++++++ .../closed/or/invalid/case4/query1.rq | 14 +++ .../closed/or/invalid/case4/query2.rq | 12 ++ .../closed/or/invalid/case4/report.ttl | 44 +++++++ .../resources/test-cases/closed/or/shacl.trig | 54 ++++++++ .../closed/or/valid/case1/query1.rq | 13 ++ .../closed/or/valid/case1/report.ttl | 12 ++ .../closed/or/valid/case2/query1.rq | 13 ++ .../closed/or/valid/case2/query2.rq | 10 ++ .../closed/or/valid/case2/report.ttl | 12 ++ .../closed/or/valid/case3/query1.rq | 13 ++ .../closed/or/valid/case3/query2.rq | 10 ++ .../closed/or/valid/case3/report.ttl | 12 ++ 59 files changed, 1263 insertions(+), 3 deletions(-) create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/shacl.trig create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/report.ttl create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query1.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query2.rq create mode 100644 core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/report.ttl diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java index a4956cfa10e..99713230cf2 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java @@ -100,10 +100,14 @@ public ClosedConstraintComponent(ClosedConstraintComponent closedConstraintCompo @Override public void toModel(Resource subject, IRI predicate, Model model, Set cycleDetection) { - if (!ignoredProperties.isEmpty() && !model.contains(getId(), SHACL.IGNORED_PROPERTIES, null)) { + + if (!ignoredProperties.isEmpty()) { model.add(subject, SHACL.IGNORED_PROPERTIES, ignoredPropertiesHead); - ShaclAstLists.listToRdf(ignoredProperties, ignoredPropertiesHead, model); + if (!model.contains(ignoredPropertiesHead, null, null)) { + ShaclAstLists.listToRdf(ignoredProperties, ignoredPropertiesHead, model); + } } + model.add(subject, SHACL.CLOSED, literal(true)); } @@ -233,7 +237,12 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections PlanNode targetNodePlanNode; if (overrideTargetNode != null) { - targetNodePlanNode = overrideTargetNode.getPlanNode(); + targetNodePlanNode = getTargetChain() + .getEffectiveTarget(scope, connectionsGroup.getRdfsSubClassOfReasoner(), + stableRandomVariableProvider) + .extend(overrideTargetNode.getPlanNode(), connectionsGroup, validationSettings.getDataGraph(), + scope, EffectiveTarget.Extend.right, + false, null); } else { PlanNode addedTargets = effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(), scope, false, null); @@ -313,6 +322,106 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections @Override public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] dataGraph, Scope scope, StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) { + + EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider); + + switch (scope) { + case none: + throw new IllegalStateException(); + case nodeShape: + + PlanNode targets = effectiveTarget.getPlanNode(connectionsGroup, dataGraph, + scope, true, null); + + // get all subjects of all triples where the predicate is not in the allAllowedPredicates set + PlanNode unorderedSelectAdded = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, + null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), + (statement -> !allAllowedPredicates.contains(statement.getPredicate()))); + + // get all subjects of all triples where the predicate is not in the allAllowedPredicates set + PlanNode unorderedSelectRemoved = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null, + null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), + (statement -> !allAllowedPredicates.contains(statement.getPredicate()))); + + // then remove any that are in the targets node + unorderedSelectAdded = new TrimToTarget(new NotValuesIn(unorderedSelectAdded, targets)); + unorderedSelectRemoved = new TrimToTarget(new NotValuesIn(unorderedSelectRemoved, targets)); + + // union and remove duplicates + PlanNode unique = Unique.getInstance(UnionNode.getInstance(unorderedSelectAdded, unorderedSelectRemoved), + false); + + // then check that the rest are actually targets + PlanNode targetFilter = effectiveTarget.getTargetFilter(connectionsGroup, + dataGraph, + unique); + + // this should now be targets that are not valid + PlanNode extend = effectiveTarget.extend(targetFilter, connectionsGroup, + dataGraph, + scope, EffectiveTarget.Extend.left, false, null); + + return UnionNode.getInstance(extend, effectiveTarget.getPlanNode(connectionsGroup, + dataGraph, scope, true, null)); + + case propertyShape: + Path path = getTargetChain().getPath().get(); + + BufferedSplitter addedTargetsBufferedSplitter = new BufferedSplitter( + effectiveTarget.getPlanNode(connectionsGroup, dataGraph, scope, false, + null)); + PlanNode addedTargets = addedTargetsBufferedSplitter.getPlanNode(); + PlanNode addedByPath = path.getAllAdded(connectionsGroup, dataGraph, null); + + addedByPath = effectiveTarget.getTargetFilter(connectionsGroup, + dataGraph, Unique.getInstance(new TrimToTarget(addedByPath), false)); + + addedByPath = new ReduceTargets(addedByPath, addedTargetsBufferedSplitter.getPlanNode()); + + addedByPath = effectiveTarget.extend(addedByPath, connectionsGroup, dataGraph, + scope, + EffectiveTarget.Extend.left, false, + null); + + PlanNode addedByValue = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, + null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> { + return !allAllowedPredicates.contains(statement.getPredicate()); + })); + + PlanNode removedByValue = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null, + null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> { + return !allAllowedPredicates.contains(statement.getPredicate()); + })); + + addedByValue = UnionNode.getInstance(addedByValue, removedByValue); + + addedByValue = getTargetChain() + .getEffectiveTarget(Scope.nodeShape, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) + .extend(addedByValue, connectionsGroup, dataGraph, Scope.nodeShape, + EffectiveTarget.Extend.left, + false, null); + + addedByValue = getTargetChain() + .getEffectiveTarget(Scope.nodeShape, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider) + .getTargetFilter(connectionsGroup, dataGraph, addedByValue); + + addedTargets = UnionNode.getInstance(addedTargets, + new TrimToTarget(new ShiftToPropertyShape(addedByValue))); + + addedTargets = UnionNode.getInstance(addedByPath, addedTargets); + addedTargets = Unique.getInstance(addedTargets, false); + + return addedTargets; + + } + throw new UnsupportedOperationException(); } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java index 222ad2ed20e..94a2667d6db 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java @@ -588,6 +588,12 @@ void referenceImplementationTestCaseValidation(TestCase testCase) { return; } + // the TopBraid SHACL API doesn't agree with other implementations on how sh:closed should work in a property + // shape + if (testCase.testCasePath.startsWith("test-cases/closed/notPropertyShape/")) { + return; + } + printTestCase(testCase); Dataset shaclDataset = DatasetFactory.create(); diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/query1.rq new file mode 100644 index 00000000000..77a55384a16 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/report.ttl new file mode 100644 index 00000000000..e0ea282215f --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case1/report.ttl @@ -0,0 +1,34 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query1.rq new file mode 100644 index 00000000000..b5613b70852 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query1.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:age 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/report.ttl new file mode 100644 index 00000000000..e0ea282215f --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case2/report.ttl @@ -0,0 +1,34 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query1.rq new file mode 100644 index 00000000000..6c642d4996c --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query2.rq new file mode 100644 index 00000000000..e54bfe68b59 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +DELETE DATA { + ex:validPerson1 ex:age2 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/report.ttl new file mode 100644 index 00000000000..e0ea282215f --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/invalid/case3/report.ttl @@ -0,0 +1,34 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/not/shacl.trig new file mode 100644 index 00000000000..71beb1895d6 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/shacl.trig @@ -0,0 +1,35 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:not + [ + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + ], + [ + sh:path ex:age ; + ], + [ + sh:path rdf:type; + ] + ; + ] + + ; + + . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/query1.rq new file mode 100644 index 00000000000..6c642d4996c --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query1.rq new file mode 100644 index 00000000000..5960b70b12d --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query1.rq new file mode 100644 index 00000000000..6c642d4996c --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query2.rq new file mode 100644 index 00000000000..90e36f9e538 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/not/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/query1.rq new file mode 100644 index 00000000000..80884c219e0 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/report.ttl new file mode 100644 index 00000000000..5a82aec88f7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case1/report.ttl @@ -0,0 +1,48 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:property [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query1.rq new file mode 100644 index 00000000000..0de45d1675d --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/report.ttl new file mode 100644 index 00000000000..5a82aec88f7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case2/report.ttl @@ -0,0 +1,48 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:property [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query1.rq new file mode 100644 index 00000000000..723d497fc83 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query1.rq @@ -0,0 +1,17 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:validPerson2 . + + ex:validPerson2 + ex:name "John" ; + ex:age 30 ; + ex:age2 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query2.rq new file mode 100644 index 00000000000..c0c7e11d300 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/query2.rq @@ -0,0 +1,11 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +DELETE DATA { + + ex:validPerson2 ex:age2 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/report.ttl new file mode 100644 index 00000000000..5a82aec88f7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case3/report.ttl @@ -0,0 +1,48 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:property [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query1.rq new file mode 100644 index 00000000000..723d497fc83 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query1.rq @@ -0,0 +1,17 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:validPerson2 . + + ex:validPerson2 + ex:name "John" ; + ex:age 30 ; + ex:age2 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query2.rq new file mode 100644 index 00000000000..f041b85bbc6 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +DELETE DATA { + ex:validPerson1 ex:knows ex:validPerson2 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/report.ttl new file mode 100644 index 00000000000..5a82aec88f7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/invalid/case4/report.ttl @@ -0,0 +1,48 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:NotConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:not [ a sh:NodeShape; + sh:property [ a sh:PropertyShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:path [ + sh:alternativePath (ex:knows (ex:knows1 ex:knows2)) + ]; + sh:property [ a sh:PropertyShape; + sh:maxCount 1; + sh:minCount 1; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:minCount 1; + sh:minInclusive 18; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:maxCount 1; + sh:path [ + sh:alternativePath (ex:father ex:mother) + ] + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] + ]; + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/shacl.trig new file mode 100644 index 00000000000..875ee913168 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/shacl.trig @@ -0,0 +1,42 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:not [ + sh:property [ + sh:path [ sh:alternativePath (ex:knows ( ex:knows1 ex:knows2 ) ) ]; + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + sh:minCount 1 ; + sh:maxCount 1 ; + ], + [ + sh:path ex:age ; + sh:minCount 1 ; + sh:minInclusive 18 ; + ], + [ + sh:path [ sh:alternativePath ( ex:father ex:mother ) ] ; + sh:maxCount 1 ; + ], + [ + sh:path rdf:type; + ] + ; + + ] ; + ] . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/query1.rq new file mode 100644 index 00000000000..51a0d479759 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query1.rq new file mode 100644 index 00000000000..cd9e09d8f20 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query1.rq @@ -0,0 +1,16 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:knows [ + ex:name "John" ; + ex:age 30 ; + ex:age2 30; + ] + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query1.rq new file mode 100644 index 00000000000..723d497fc83 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query1.rq @@ -0,0 +1,17 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:knows ex:validPerson2 . + + ex:validPerson2 + ex:name "John" ; + ex:age 30 ; + ex:age2 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query2.rq new file mode 100644 index 00000000000..6773d4e93e4 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson2 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/notPropertyShape/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/query1.rq new file mode 100644 index 00000000000..3227d77c657 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/query1.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/report.ttl new file mode 100644 index 00000000000..916db153eaf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case1/report.ttl @@ -0,0 +1,44 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:OrConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:or ([ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored_or ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name_or + ], [ a sh:PropertyShape; + sh:path ex:age_or + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]); + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query1.rq new file mode 100644 index 00000000000..38f97e41726 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:notAllowedProperty "This property is not allowed" ; + ex:age 30 . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query2.rq new file mode 100644 index 00000000000..14a830f5b7e --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person. +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/report.ttl new file mode 100644 index 00000000000..916db153eaf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case2/report.ttl @@ -0,0 +1,44 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:OrConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:or ([ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored_or ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name_or + ], [ a sh:PropertyShape; + sh:path ex:age_or + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]); + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query2.rq new file mode 100644 index 00000000000..729dc51fbe7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/query2.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:mother ex:Shelly; + ex:father ex:Hubert; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/report.ttl new file mode 100644 index 00000000000..916db153eaf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case3/report.ttl @@ -0,0 +1,44 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:OrConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:or ([ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored_or ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name_or + ], [ a sh:PropertyShape; + sh:path ex:age_or + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]); + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query1.rq new file mode 100644 index 00000000000..77a55384a16 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + ex:thisPropertyIsIgnored "Ignored by sh:closed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query2.rq new file mode 100644 index 00000000000..074a12c1e36 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/query2.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:notAllowedProperty "This property is not allowed" ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/report.ttl new file mode 100644 index 00000000000..916db153eaf --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/invalid/case4/report.ttl @@ -0,0 +1,44 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms false; + sh:result [ a sh:ValidationResult; + rsx:shapesGraph rdf4j:SHACLShapeGraph; + sh:focusNode ex:validPerson1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:OrConstraintComponent; + sh:sourceShape ex:Person; + sh:value ex:validPerson1 + ] . + +ex:Person a sh:NodeShape; + sh:or ([ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name + ], [ a sh:PropertyShape; + sh:path ex:age + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ] [ a sh:NodeShape; + sh:closed true; + sh:ignoredProperties (ex:thisPropertyIsIgnored_or ex:thisPropertyIsAlsoIgnored); + sh:property [ a sh:PropertyShape; + sh:path ex:name_or + ], [ a sh:PropertyShape; + sh:path ex:age_or + ], [ a sh:PropertyShape; + sh:path rdf:type + ] + ]); + sh:targetClass ex:Person . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/shacl.trig b/core/sail/shacl/src/test/resources/test-cases/closed/or/shacl.trig new file mode 100644 index 00000000000..6db5c8b96d7 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/shacl.trig @@ -0,0 +1,54 @@ +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix dash: . +@prefix rdf4j: . + +rdf4j:SHACLShapeGraph { + + ex:Person + a rdfs:Class, sh:NodeShape ; + sh:or ( + [ + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name ; + ], + [ + sh:path ex:age ; + ], + [ + sh:path rdf:type; + ] + ; + ] + + [ + + sh:closed true; + sh:ignoredProperties ( ex:thisPropertyIsIgnored_or ex:thisPropertyIsAlsoIgnored ) ; + sh:property + [ + sh:path ex:name_or ; + ], + [ + sh:path ex:age_or ; + ], + [ + sh:path rdf:type; + ] + ; + + ] + + + ); + + . + +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case1/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query1.rq new file mode 100644 index 00000000000..2ea9878fc43 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query2.rq new file mode 100644 index 00000000000..dd676e3dcff --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case2/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query1.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query1.rq new file mode 100644 index 00000000000..e1c655aa501 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 a ex:Person ; + ex:name "John" ; + ex:age 30 ; + . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query2.rq b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query2.rq new file mode 100644 index 00000000000..90e36f9e538 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/query2.rq @@ -0,0 +1,10 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + ex:validPerson1 ex:thisPropertyIsIgnored "Ignored by sh:closed" . +} diff --git a/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/report.ttl b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/report.ttl new file mode 100644 index 00000000000..c0518dc88b5 --- /dev/null +++ b/core/sail/shacl/src/test/resources/test-cases/closed/or/valid/case3/report.ttl @@ -0,0 +1,12 @@ +@prefix ex: . +@prefix foaf: . +@prefix xsd: . +@prefix sh: . +@prefix rdf: . +@prefix rdfs: . +@prefix rsx: . +@prefix rdf4j: . + +[] a sh:ValidationReport; + rdf4j:truncated false; + sh:conforms true . From 09afe01750005aab48576b351085fae32ad556ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Thu, 11 Apr 2024 11:14:13 +0200 Subject: [PATCH 3/4] GH-1112 minor optimizations --- .../ClassConstraintComponent.java | 2 +- .../ClosedConstraintComponent.java | 65 +++++++++++-------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java index 6e9224cdff4..94ea3e2be3c 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClassConstraintComponent.java @@ -186,7 +186,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections stableRandomVariableProvider) .extend(deletedTypes, connectionsGroup, validationSettings.getDataGraph(), scope, EffectiveTarget.Extend.left, false, null); - addedTargets = UnionNode.getInstance(addedTargets, new TrimToTarget(deletedTypes)); + addedTargets = UnionNode.getInstance(addedTargets, deletedTypes); } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java index 99713230cf2..ab532fec32e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java @@ -39,7 +39,6 @@ import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BulkedExternalInnerJoin; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ExternalFilterByQuery; -import org.eclipse.rdf4j.sail.shacl.ast.planNodes.NotValuesIn; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ReduceTargets; @@ -233,6 +232,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections return falseNode1; } else { + assert scope == Scope.nodeShape; PlanNode targetNodePlanNode; @@ -248,18 +248,17 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections scope, false, null); // get all subjects of all triples where the predicate is not in the allAllowedPredicates set - UnorderedSelect unorderedSelect = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, + PlanNode unorderedSelect = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, null, validationSettings.getDataGraph(), UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> { return !allAllowedPredicates.contains(statement.getPredicate()); })); // then remove any that are in the addedTargets node - NotValuesIn notValuesIn = new NotValuesIn(unorderedSelect, addedTargets); + PlanNode notValuesIn = new ReduceTargets(unorderedSelect, addedTargets); - // trim to target and remove duplicates - TrimToTarget trimToTarget = new TrimToTarget(notValuesIn); - PlanNode unique = Unique.getInstance(trimToTarget, false); + // remove duplicates + PlanNode unique = Unique.getInstance(notValuesIn, false); // then check that the rest are actually targets PlanNode targetFilter = effectiveTarget.getTargetFilter(connectionsGroup, @@ -286,7 +285,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter)); BulkedExternalInnerJoin bulkedExternalInnerJoin = new BulkedExternalInnerJoin( - Unique.getInstance(new TrimToTarget(targetNodePlanNode), false), + Unique.getInstance(targetNodePlanNode, false), connectionsGroup.getBaseConnection(), validationSettings.getDataGraph(), sparqlFragment, @@ -331,41 +330,51 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] throw new IllegalStateException(); case nodeShape: - PlanNode targets = effectiveTarget.getPlanNode(connectionsGroup, dataGraph, - scope, true, null); - - // get all subjects of all triples where the predicate is not in the allAllowedPredicates set - PlanNode unorderedSelectAdded = new UnorderedSelect(connectionsGroup.getAddedStatements(), null, null, - null, dataGraph, - UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), - (statement -> !allAllowedPredicates.contains(statement.getPredicate()))); - + BufferedSplitter targets = new BufferedSplitter( + effectiveTarget.getPlanNode(connectionsGroup, dataGraph, scope, false, + null)); // get all subjects of all triples where the predicate is not in the allAllowedPredicates set - PlanNode unorderedSelectRemoved = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null, + PlanNode statementsNotMatchingPredicateList = new UnorderedSelect(connectionsGroup.getAddedStatements(), + null, null, null, dataGraph, UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), (statement -> !allAllowedPredicates.contains(statement.getPredicate()))); // then remove any that are in the targets node - unorderedSelectAdded = new TrimToTarget(new NotValuesIn(unorderedSelectAdded, targets)); - unorderedSelectRemoved = new TrimToTarget(new NotValuesIn(unorderedSelectRemoved, targets)); - - // union and remove duplicates - PlanNode unique = Unique.getInstance(UnionNode.getInstance(unorderedSelectAdded, unorderedSelectRemoved), - false); + statementsNotMatchingPredicateList = new ReduceTargets(statementsNotMatchingPredicateList, + targets.getPlanNode()); // then check that the rest are actually targets - PlanNode targetFilter = effectiveTarget.getTargetFilter(connectionsGroup, + statementsNotMatchingPredicateList = effectiveTarget.getTargetFilter(connectionsGroup, dataGraph, - unique); + statementsNotMatchingPredicateList); + + if (connectionsGroup.getStats().hasRemoved()) { + + // get all subjects of all triples where the predicate is not in the allAllowedPredicates set + PlanNode removed = new UnorderedSelect(connectionsGroup.getRemovedStatements(), null, null, + null, dataGraph, + UnorderedSelect.Mapper.SubjectScopedMapper.getFunction(scope), + (statement -> !allAllowedPredicates.contains(statement.getPredicate()))); + + removed = new ReduceTargets(removed, targets.getPlanNode()); + + // then check that the rest are actually targets + removed = effectiveTarget.getTargetFilter(connectionsGroup, dataGraph, removed); + + statementsNotMatchingPredicateList = UnionNode.getInstance(statementsNotMatchingPredicateList, removed); + + } + + // union and remove duplicates + PlanNode unique = Unique.getInstance(statementsNotMatchingPredicateList, false); // this should now be targets that are not valid - PlanNode extend = effectiveTarget.extend(targetFilter, connectionsGroup, + PlanNode extend = effectiveTarget.extend(unique, connectionsGroup, dataGraph, scope, EffectiveTarget.Extend.left, false, null); - return UnionNode.getInstance(extend, effectiveTarget.getPlanNode(connectionsGroup, - dataGraph, scope, true, null)); + return extend; case propertyShape: Path path = getTargetChain().getPath().get(); From 1dc2438217c1f7a1218d237e20434766ba77949b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=CC=8Avard=20Ottestad?= Date: Mon, 15 Apr 2024 09:27:11 +0200 Subject: [PATCH 4/4] GH-1112 support for SPARQL based validation --- .../rdf4j/sail/shacl/ast/ValidationQuery.java | 72 +++++++++++-- .../ClosedConstraintComponent.java | 100 ++++++++++++++++++ 2 files changed, 164 insertions(+), 8 deletions(-) diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ValidationQuery.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ValidationQuery.java index 13a30e3c88c..64fe604f64d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ValidationQuery.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ValidationQuery.java @@ -16,24 +16,27 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.function.Function; import org.eclipse.rdf4j.model.Namespace; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Select; -import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationReportNode; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple; import org.eclipse.rdf4j.sail.shacl.results.ValidationResult; public class ValidationQuery { private final Set namespaces = new HashSet<>(); + private ValidationResultGenerator validationResultGenerator; private String query; private ConstraintComponent.Scope scope; private ConstraintComponent.Scope scope_validationReport; @@ -54,6 +57,7 @@ public class ValidationQuery { private Severity severity; private Shape shape; + private List> extraVariables = List.of(); public ValidationQuery(Collection namespaces, String query, List> targets, Variable value, @@ -90,6 +94,8 @@ public ValidationQuery(Collection namespaces, String query, List namespaces, String query, ConstraintComponent.Scope scope, @@ -101,6 +107,7 @@ public ValidationQuery(Set namespaces, String query, ConstraintCompon this.variables = Collections.unmodifiableList(variables); this.targetIndex = targetIndex; this.valueIndex = valueIndex; + this.validationResultGenerator = new ValidationResultGenerator(); } /** @@ -169,41 +176,78 @@ public PlanNode getValidationPlan(SailConnection baseConnection, Resource[] data Select select = new Select(baseConnection, fullQueryString, bindings -> { + var validationResultFunction = validationResultGenerator.getValidationTupleValidationResultFunction(this, + shapesGraphs, bindings); + + ValidationTuple validationTuple; + if (scope_validationReport == ConstraintComponent.Scope.propertyShape) { if (propertyShapeWithValue_validationReport) { - return new ValidationTuple(bindings.getValue(getTargetVariable(true)), + validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)), bindings.getValue(getValueVariable(true)), scope_validationReport, true, dataGraph); } else { - return new ValidationTuple(bindings.getValue(getTargetVariable(true)), + validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)), scope_validationReport, false, dataGraph); } } else { - return new ValidationTuple(bindings.getValue(getTargetVariable(true)), + validationTuple = new ValidationTuple(bindings.getValue(getTargetVariable(true)), scope_validationReport, true, dataGraph); } + return validationTuple.addValidationResult(validationResultFunction); + }, dataGraph); - return new ValidationReportNode(select, t -> { - return new ValidationResult(t.getActiveTarget(), t.getValue(), shape, - constraintComponent_validationReport, severity, t.getScope(), t.getContexts(), shapesGraphs); - }); + return select; + + } + + public static class ValidationResultGenerator { + + public Function getValidationTupleValidationResultFunction( + ValidationQuery validationQuery, Resource[] shapesGraphs, BindingSet bindings) { + return t -> new ValidationResult(t.getActiveTarget(), t.getValue(), validationQuery.shape, + validationQuery.constraintComponent_validationReport, validationQuery.severity, t.getScope(), + t.getContexts(), shapesGraphs); + } + + } + public void setValidationResultGenerator(List> extraVariables, + ValidationResultGenerator validationResultGenerator) { + this.validationResultGenerator = validationResultGenerator; + this.extraVariables = extraVariables; } private String getFullQueryString() { + String extraVariablesString; + if (!extraVariables.isEmpty()) { + Optional reduce = extraVariables.stream() + .map(Variable::asSparqlVariable) + .reduce((a, b) -> a + " " + b); + if (reduce.isPresent()) { + extraVariablesString = reduce.get() + " "; + } else { + extraVariablesString = ""; + } + } else + extraVariablesString = ""; + if (scope_validationReport == ConstraintComponent.Scope.propertyShape && propertyShapeWithValue_validationReport) { + return ShaclPrefixParser.toSparqlPrefixes(namespaces) + "\nSELECT DISTINCT " + "?" + getTargetVariable(true) + " " + "?" + getValueVariable(true) + " " + + extraVariablesString + "WHERE {\n" + query + "\n}"; } else { return ShaclPrefixParser.toSparqlPrefixes(namespaces) + "\nSELECT DISTINCT " + "?" + getTargetVariable(true) + " " + + extraVariablesString + "WHERE {\n" + query + "\n}"; } } @@ -267,6 +311,18 @@ public void makeCurrentStateValidationReport() { propertyShapeWithValue_validationReport = propertyShapeWithValue; } + public Shape getShape() { + return shape; + } + + public Severity getSeverity() { + return severity; + } + + public ConstraintComponent getConstraintComponent_validationReport() { + return constraintComponent_validationReport; + } + // used for sh:deactivated public static class Deactivated extends ValidationQuery { diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java index ab532fec32e..ac4fa3f2e4a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/ClosedConstraintComponent.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import org.eclipse.rdf4j.model.IRI; @@ -34,6 +35,8 @@ import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; +import org.eclipse.rdf4j.sail.shacl.ast.ValidationApproach; +import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery; import org.eclipse.rdf4j.sail.shacl.ast.paths.Path; import org.eclipse.rdf4j.sail.shacl.ast.paths.SimplePath; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BufferedSplitter; @@ -434,6 +437,103 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] throw new UnsupportedOperationException(); } + @Override + public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connectionsGroup, + ValidationSettings validationSettings, boolean negatePlan, boolean negateChildren, Scope scope) { + StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider(); + + EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider); + String query = effectiveTarget.getQuery(false); + + StatementMatcher.Variable predicateVariable = stableRandomVariableProvider.next(); + StatementMatcher.Variable objectVariable = stableRandomVariableProvider.next(); + + StatementMatcher.Variable value; + + if (scope == Scope.nodeShape) { + + value = null; + + var target = effectiveTarget.getTargetVar(); + + query += "\n" + getFilter(target, predicateVariable, objectVariable); + + } else { + value = new StatementMatcher.Variable<>("value"); + + SparqlFragment sparqlFragment = getTargetChain().getPath() + .map(p -> p.getTargetQueryFragment(effectiveTarget.getTargetVar(), value, + connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of())) + .orElseThrow(IllegalStateException::new); + + String pathQuery = sparqlFragment.getFragment(); + + query += "\n" + pathQuery; + query += "\n" + getFilter(value, predicateVariable, objectVariable); + } + + var allTargetVariables = effectiveTarget.getAllTargetVariables(); + + ValidationQuery validationQuery = new ValidationQuery(getTargetChain().getNamespaces(), query, + allTargetVariables, value, scope, this, + null, null); + + if (produceValidationReports) { + validationQuery = validationQuery + .withShape(shape) + .withSeverity(shape.getSeverity()); + + validationQuery.makeCurrentStateValidationReport(); + + validationQuery.setValidationResultGenerator(List.of(predicateVariable, objectVariable), + new ValidationQuery.ValidationResultGenerator() { + @Override + public Function getValidationTupleValidationResultFunction( + ValidationQuery validationQuery, Resource[] shapesGraphs, BindingSet bindings) { + Function validationResultFunction = t -> { + ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), + bindings.getValue(objectVariable.getName()), validationQuery.getShape(), + validationQuery.getConstraintComponent_validationReport(), + validationQuery.getSeverity(), t.getScope(), t.getContexts(), shapesGraphs); + validationResult.setPathIri(bindings.getValue(predicateVariable.getName())); + return validationResult; + }; + return validationResultFunction; + } + }); + + } + + return validationQuery; + } + + private String getFilter(StatementMatcher.Variable target, + StatementMatcher.Variable predicateVariable, StatementMatcher.Variable objectVariable) { + + SparqlFragment bgp = SparqlFragment.bgp(List.of(), + target.asSparqlVariable() + " " + predicateVariable.asSparqlVariable() + " " + + objectVariable.asSparqlVariable() + ".", + List.of()); + String notInSparqlFilter = "FILTER( " + predicateVariable.asSparqlVariable() + " NOT IN( " + + allAllowedPredicates.stream().map(p -> "<" + p.toString() + ">").collect(Collectors.joining(", ")) + + " ) )"; + SparqlFragment sparqlFragmentFilter = SparqlFragment.bgp(List.of(), notInSparqlFilter, List.of()); + SparqlFragment sparqlFragment = SparqlFragment.join(List.of(bgp, sparqlFragmentFilter)); + + return sparqlFragment.getFragment(); + } + + @Override + public ValidationApproach getPreferredValidationApproach(ConnectionsGroup connectionsGroup) { + return super.getPreferredValidationApproach(connectionsGroup); + } + + @Override + public ValidationApproach getOptimalBulkValidationApproach() { + return ValidationApproach.SPARQL; + } + @Override public boolean requiresEvaluation(ConnectionsGroup connectionsGroup, Scope scope, Resource[] dataGraph, StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) {