Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-4489 User defined SPARQL queries in SHACL constraints and targets #4490

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ public static List<IRI> getSupportedShaclPredicates() {
SHACL.QUALIFIED_VALUE_SHAPE,
SHACL.SHAPES_GRAPH,
SHACL.MESSAGE,
SHACL.DECLARE,
SHACL.SPARQL,
SHACL.SELECT,
SHACL.PREFIXES,
SHACL.PREFIX_PROP,
SHACL.NAMESPACE_PROP,
DASH.hasValueIn,
RSX.targetShape
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1102,10 +1102,6 @@ public Shape getShape() {
return shape;
}

public PlanNode getPlanNode() {
return planNode;
}

public boolean hasPlanNode() {
return !(planNode instanceof EmptyNode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,77 +16,61 @@
import org.eclipse.rdf4j.model.vocabulary.SHACL;

public enum SourceConstraintComponent {
MaxCountConstraintComponent(SHACL.MAX_COUNT_CONSTRAINT_COMPONENT, ConstraintType.Cardinality, false),
MinCountConstraintComponent(SHACL.MIN_COUNT_CONSTRAINT_COMPONENT, ConstraintType.Cardinality, false),

DatatypeConstraintComponent(SHACL.DATATYPE_CONSTRAINT_COMPONENT, ConstraintType.ValueType, true),
NodeKindConstraintComponent(SHACL.NODE_KIND_CONSTRAINT_COMPONENT, ConstraintType.ValueType, true),
ClassConstraintComponent(SHACL.CLASS_CONSTRAINT_COMPONENT, ConstraintType.ValueType, true),

PatternConstraintComponent(SHACL.PATTERN_CONSTRAINT_COMPONENT, ConstraintType.StringBased, true),
UniqueLangConstraintComponent(SHACL.UNIQUE_LANG_CONSTRAINT_COMPONENT, ConstraintType.StringBased, false),
LanguageInConstraintComponent(SHACL.LANGUAGE_IN_CONSTRAINT_COMPONENT, ConstraintType.StringBased, true),
MaxLengthConstraintComponent(SHACL.MAX_LENGTH_CONSTRAINT_COMPONENT, ConstraintType.StringBased, true),
MinLengthConstraintComponent(SHACL.MIN_LENGTH_CONSTRAINT_COMPONENT, ConstraintType.StringBased, true),

InConstraintComponent(SHACL.IN_CONSTRAINT_COMPONENT, ConstraintType.Other, true),
HasValueConstraintComponent(SHACL.HAS_VALUE_CONSTRAINT_COMPONENT, ConstraintType.Other, false),
HasValueInConstraintComponent(DASH.HasValueInConstraintComponent, ConstraintType.Other, false),
ClosedConstraintComponent(SHACL.CLOSED_CONSTRAINT_COMPONENT, ConstraintType.Other, true),

MinExclusiveConstraintComponent(SHACL.MIN_EXCLUSIVE_CONSTRAINT_COMPONENT, ConstraintType.ValueRange, true),
MaxExclusiveConstraintComponent(SHACL.MAX_EXCLUSIVE_CONSTRAINT_COMPONENT, ConstraintType.ValueRange, true),
MaxInclusiveConstraintComponent(SHACL.MAX_INCLUSIVE_CONSTRAINT_COMPONENT, ConstraintType.ValueRange, true),
MinInclusiveConstraintComponent(SHACL.MIN_INCLUSIVE_CONSTRAINT_COMPONENT, ConstraintType.ValueRange, true),

AndConstraintComponent(SHACL.AND_CONSTRAINT_COMPONENT, ConstraintType.Logical, true),
OrConstraintComponent(SHACL.OR_CONSTRAINT_COMPONENT, ConstraintType.Logical, true),
NotConstraintComponent(SHACL.NOT_CONSTRAINT_COMPONENT, ConstraintType.Logical, true),
XoneConstraintComponent(SHACL.XONE_CONSTRAINT_COMPONENT, ConstraintType.Logical, true),

DisjointConstraintComponent(SHACL.DISJOINT_CONSTRAINT_COMPONENT, ConstraintType.PropertyPair, true),
EqualsConstraintComponent(SHACL.EQUALS_CONSTRAINT_COMPONENT, ConstraintType.PropertyPair, true),
LessThanConstraintComponent(SHACL.LESS_THAN_CONSTRAINT_COMPONENT, ConstraintType.PropertyPair, true),
LessThanOrEqualsConstraintComponent(SHACL.LESS_THAN_OR_EQUALS_CONSTRAINT_COMPONENT, ConstraintType.PropertyPair,
MaxCountConstraintComponent(SHACL.MAX_COUNT_CONSTRAINT_COMPONENT, false),
MinCountConstraintComponent(SHACL.MIN_COUNT_CONSTRAINT_COMPONENT, false),

DatatypeConstraintComponent(SHACL.DATATYPE_CONSTRAINT_COMPONENT, true),
NodeKindConstraintComponent(SHACL.NODE_KIND_CONSTRAINT_COMPONENT, true),
ClassConstraintComponent(SHACL.CLASS_CONSTRAINT_COMPONENT, true),

PatternConstraintComponent(SHACL.PATTERN_CONSTRAINT_COMPONENT, true),
UniqueLangConstraintComponent(SHACL.UNIQUE_LANG_CONSTRAINT_COMPONENT, false),
LanguageInConstraintComponent(SHACL.LANGUAGE_IN_CONSTRAINT_COMPONENT, true),
MaxLengthConstraintComponent(SHACL.MAX_LENGTH_CONSTRAINT_COMPONENT, true),
MinLengthConstraintComponent(SHACL.MIN_LENGTH_CONSTRAINT_COMPONENT, true),

InConstraintComponent(SHACL.IN_CONSTRAINT_COMPONENT, true),
HasValueConstraintComponent(SHACL.HAS_VALUE_CONSTRAINT_COMPONENT, false),
HasValueInConstraintComponent(DASH.HasValueInConstraintComponent, false),
ClosedConstraintComponent(SHACL.CLOSED_CONSTRAINT_COMPONENT, true),

MinExclusiveConstraintComponent(SHACL.MIN_EXCLUSIVE_CONSTRAINT_COMPONENT, true),
MaxExclusiveConstraintComponent(SHACL.MAX_EXCLUSIVE_CONSTRAINT_COMPONENT, true),
MaxInclusiveConstraintComponent(SHACL.MAX_INCLUSIVE_CONSTRAINT_COMPONENT, true),
MinInclusiveConstraintComponent(SHACL.MIN_INCLUSIVE_CONSTRAINT_COMPONENT, true),

AndConstraintComponent(SHACL.AND_CONSTRAINT_COMPONENT, true),
OrConstraintComponent(SHACL.OR_CONSTRAINT_COMPONENT, true),
NotConstraintComponent(SHACL.NOT_CONSTRAINT_COMPONENT, true),
XoneConstraintComponent(SHACL.XONE_CONSTRAINT_COMPONENT, true),

DisjointConstraintComponent(SHACL.DISJOINT_CONSTRAINT_COMPONENT, true),
EqualsConstraintComponent(SHACL.EQUALS_CONSTRAINT_COMPONENT, true),
LessThanConstraintComponent(SHACL.LESS_THAN_CONSTRAINT_COMPONENT, true),
LessThanOrEqualsConstraintComponent(SHACL.LESS_THAN_OR_EQUALS_CONSTRAINT_COMPONENT,
true),

QualifiedMaxCountConstraintComponent(SHACL.QUALIFIED_MAX_COUNT_CONSTRAINT_COMPONENT, ConstraintType.ShapeBased,
QualifiedMaxCountConstraintComponent(SHACL.QUALIFIED_MAX_COUNT_CONSTRAINT_COMPONENT,
false),
QualifiedMinCountConstraintComponent(SHACL.QUALIFIED_MIN_COUNT_CONSTRAINT_COMPONENT, ConstraintType.ShapeBased,
QualifiedMinCountConstraintComponent(SHACL.QUALIFIED_MIN_COUNT_CONSTRAINT_COMPONENT,
false),
NodeConstraintComponent(SHACL.NODE_CONSTRAINT_COMPONENT, ConstraintType.ShapeBased, true),
PropertyConstraintComponent(SHACL.PROPERTY_CONSTRAINT_COMPONENT, ConstraintType.ShapeBased, false);
NodeConstraintComponent(SHACL.NODE_CONSTRAINT_COMPONENT, true),
PropertyConstraintComponent(SHACL.PROPERTY_CONSTRAINT_COMPONENT, false),

SPARQLConstraintComponent(SHACL.SPARQL_CONSTRAINT_COMPONENT, true);

private final IRI iri;
private final ConstraintType constraintType;
private final boolean producesValidationResultValue;

SourceConstraintComponent(IRI iri, ConstraintType constraintType, boolean producesValidationResultValue) {
SourceConstraintComponent(IRI iri, boolean producesValidationResultValue) {
this.iri = iri;
this.constraintType = constraintType;
this.producesValidationResultValue = producesValidationResultValue;
}

public IRI getIri() {
return iri;
}

public ConstraintType getConstraintType() {
return constraintType;
}

public enum ConstraintType {
ValueType,
Cardinality,
ValueRange,
StringBased,
PropertyPair,
Logical,
ShapeBased,
Other

}

public boolean producesValidationResultValue() {
return producesValidationResultValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,13 @@

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.eclipse.rdf4j.model.Resource;

public class Cache {

Map<Resource, Shape> cache = new HashMap<>();

public Shape computeIfAbsent(Resource id, Function<Resource, Shape> mappingFunction) {
return cache.computeIfAbsent(id, mappingFunction);
}

public Shape get(Resource id) {
return cache.get(id);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connection
validationSettings, negatePlan,
negateChildren, Scope.nodeShape);
if (!(c instanceof PropertyShape)) {
return validationQuery1.withConstraintComponent(c.getConstraintComponent());
return validationQuery1.withConstraintComponent(c);
}
return validationQuery1;
})
Expand Down Expand Up @@ -172,7 +172,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections

validationPlanNode = new ValidationReportNode(validationPlanNode, t -> {
return new ValidationResult(t.getActiveTarget(), t.getActiveTarget(), this,
constraintComponent.getConstraintComponent(), getSeverity(), t.getScope(), t.getContexts(),
constraintComponent, getSeverity(), t.getScope(), t.getContexts(),
getContexts());
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.sail.shacl.ShaclSail;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.ValidationSettings;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable;
import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent;
Expand Down Expand Up @@ -149,7 +150,7 @@ public ValidationQuery generateSparqlValidationQuery(ConnectionsGroup connection
validationSettings, negatePlan,
negateChildren, Scope.propertyShape);
if (!(c instanceof PropertyShape)) {
return validationQuery1.withConstraintComponent(c.getConstraintComponent());
return validationQuery1.withConstraintComponent(c);
}
return validationQuery1;
})
Expand Down Expand Up @@ -200,7 +201,7 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections
if (!(constraintComponent instanceof PropertyShape)) {
validationPlanNode = new ValidationReportNode(validationPlanNode, t -> {
return new ValidationResult(t.getActiveTarget(), t.getValue(), this,
constraintComponent.getConstraintComponent(), getSeverity(), t.getScope(), t.getContexts(),
constraintComponent, getSeverity(), t.getScope(), t.getContexts(),
getContexts());
});
}
Expand Down Expand Up @@ -301,4 +302,9 @@ public SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable<Value> subj

}

@Override
public SourceConstraintComponent getConstraintComponent() {
return SourceConstraintComponent.PropertyConstraintComponent;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright (c) 2023 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;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.DynamicModel;
import org.eclipse.rdf4j.model.impl.LinkedHashModelFactory;
import org.eclipse.rdf4j.model.impl.SimpleNamespace;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;

@InternalUseOnly
public class ShaclPrefixParser {

private ShaclPrefixParser() {
}

public static Namespaces extractNamespaces(Resource id, ShapeSource shapeSource) {
var shaclPrefixes = new Namespaces();

try (Stream<Value> objects = shapeSource.getObjects(id, ShapeSource.Predicates.PREFIXES)) {
objects.forEach(prefix -> {
if (!(prefix instanceof Resource)) {
throw new IllegalStateException("sh:prefixes must be an Resource for constraint component " + id);
}
shaclPrefixes.model.add(id, SHACL.PREFIXES, prefix);

try (Stream<Value> declareObjects = shapeSource.getObjects(((Resource) prefix),
ShapeSource.Predicates.DECLARE)) {
declareObjects.forEach(declaration -> {
if (!(declaration instanceof Resource)) {
throw new IllegalStateException("sh:declare must be a Resource for " + prefix);
}

shaclPrefixes.model.add((Resource) prefix, SHACL.DECLARE, declaration);

String namespacePrefix = null;
String namespaceName = null;

try (Stream<Value> prefixPropObjects = shapeSource.getObjects(((Resource) declaration),
ShapeSource.Predicates.PREFIX_PROP)) {
namespacePrefix = prefixPropObjects
.map(literal -> {
if (!(literal instanceof Literal)) {
throw new IllegalStateException(
"sh:prefix must be a Literal for " + declaration);
}
shaclPrefixes.model.add((Resource) declaration, SHACL.PREFIX_PROP, literal);
return literal.stringValue();
})
.findFirst()
.orElseThrow(() -> new IllegalStateException(
"sh:prefix must have a value for " + declaration));
}

try (Stream<Value> namespacePropObjects = shapeSource.getObjects(((Resource) declaration),
ShapeSource.Predicates.NAMESPACE_PROP)) {
namespaceName = namespacePropObjects
.map(literal -> {
if (!(literal instanceof Literal)) {
throw new IllegalStateException(
"sh:namespace must be a Literal for " + declaration);
}
shaclPrefixes.model.add((Resource) declaration, SHACL.NAMESPACE_PROP, literal);
return literal.stringValue();
})
.findFirst()
.orElseThrow(() -> new IllegalStateException(
"sh:namespace must have a value for " + declaration));
}

shaclPrefixes.namespaces.add(new SimpleNamespace(namespacePrefix, namespaceName));

});
}

});
}

return shaclPrefixes;
}

public static String toSparqlPrefixes(Collection<Namespace> namespaces) {
StringBuilder sb = new StringBuilder();
namespaces.forEach(namespace -> {
sb.append("PREFIX ");
sb.append(namespace.getPrefix());
sb.append(": <");
sb.append(namespace.getName());
sb.append("> \n");
});
sb.append("\n");
return sb.toString();
}

public static final class Namespaces {
private final Set<Namespace> namespaces = new HashSet<>();
private final Model model = new DynamicModel(new LinkedHashModelFactory());

private Namespaces() {
}

public Set<Namespace> getNamespaces() {
return namespaces;
}

public Model getModel() {
return model;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class ShaclProperties {

private static final Logger logger = LoggerFactory.getLogger(ShaclProperties.class);

private Resource id;
private IRI type;

private final List<IRI> clazz = new ArrayList<>();
Expand Down Expand Up @@ -91,10 +92,10 @@ public class ShaclProperties {
boolean closed = false;
private Resource ignoredProperties;

private Resource id;

private final List<Literal> message = new ArrayList<>();

private final List<Resource> sparql = new ArrayList<>();

public ShaclProperties() {
}

Expand Down Expand Up @@ -315,6 +316,12 @@ public ShaclProperties(Resource id, ShapeSource connection) {
case "http://rdf4j.org/shacl-extensions#targetShape":
targetShape.add((Resource) object);
break;
case "http://www.w3.org/ns/shacl#sparql":
if (!object.isResource()) {
throw new IllegalStateException("Object is not a resource: " + statement);
}
sparql.add((Resource) object);
break;

default:
if (predicate.startsWith(SHACL.NAMESPACE)) {
Expand Down Expand Up @@ -513,4 +520,8 @@ public Long getQualifiedMaxCount() {
public Boolean getQualifiedValueShapesDisjoint() {
return qualifiedValueShapesDisjoint;
}

public List<Resource> getSparql() {
return sparql;
}
}
Loading