Skip to content

Commit eae9e0a

Browse files
committed
closes GH-5114 fix for SPARQL constraint values being null
1 parent 1d3fb90 commit eae9e0a

6 files changed

Lines changed: 100 additions & 89 deletions

File tree

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

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,53 @@
1616
import org.eclipse.rdf4j.model.vocabulary.SHACL;
1717

1818
public enum SourceConstraintComponent {
19-
MaxCountConstraintComponent(SHACL.MAX_COUNT_CONSTRAINT_COMPONENT, false),
20-
MinCountConstraintComponent(SHACL.MIN_COUNT_CONSTRAINT_COMPONENT, false),
21-
22-
DatatypeConstraintComponent(SHACL.DATATYPE_CONSTRAINT_COMPONENT, true),
23-
NodeKindConstraintComponent(SHACL.NODE_KIND_CONSTRAINT_COMPONENT, true),
24-
ClassConstraintComponent(SHACL.CLASS_CONSTRAINT_COMPONENT, true),
25-
26-
PatternConstraintComponent(SHACL.PATTERN_CONSTRAINT_COMPONENT, true),
27-
UniqueLangConstraintComponent(SHACL.UNIQUE_LANG_CONSTRAINT_COMPONENT, false),
28-
LanguageInConstraintComponent(SHACL.LANGUAGE_IN_CONSTRAINT_COMPONENT, true),
29-
MaxLengthConstraintComponent(SHACL.MAX_LENGTH_CONSTRAINT_COMPONENT, true),
30-
MinLengthConstraintComponent(SHACL.MIN_LENGTH_CONSTRAINT_COMPONENT, true),
31-
32-
InConstraintComponent(SHACL.IN_CONSTRAINT_COMPONENT, true),
33-
HasValueConstraintComponent(SHACL.HAS_VALUE_CONSTRAINT_COMPONENT, false),
34-
HasValueInConstraintComponent(DASH.HasValueInConstraintComponent, false),
35-
ClosedConstraintComponent(SHACL.CLOSED_CONSTRAINT_COMPONENT, true),
36-
37-
MinExclusiveConstraintComponent(SHACL.MIN_EXCLUSIVE_CONSTRAINT_COMPONENT, true),
38-
MaxExclusiveConstraintComponent(SHACL.MAX_EXCLUSIVE_CONSTRAINT_COMPONENT, true),
39-
MaxInclusiveConstraintComponent(SHACL.MAX_INCLUSIVE_CONSTRAINT_COMPONENT, true),
40-
MinInclusiveConstraintComponent(SHACL.MIN_INCLUSIVE_CONSTRAINT_COMPONENT, true),
41-
42-
AndConstraintComponent(SHACL.AND_CONSTRAINT_COMPONENT, true),
43-
OrConstraintComponent(SHACL.OR_CONSTRAINT_COMPONENT, true),
44-
NotConstraintComponent(SHACL.NOT_CONSTRAINT_COMPONENT, true),
45-
XoneConstraintComponent(SHACL.XONE_CONSTRAINT_COMPONENT, true),
46-
47-
DisjointConstraintComponent(SHACL.DISJOINT_CONSTRAINT_COMPONENT, true),
48-
EqualsConstraintComponent(SHACL.EQUALS_CONSTRAINT_COMPONENT, true),
49-
LessThanConstraintComponent(SHACL.LESS_THAN_CONSTRAINT_COMPONENT, true),
19+
MaxCountConstraintComponent(SHACL.MAX_COUNT_CONSTRAINT_COMPONENT, ProducesValidationResultValue.NEVER),
20+
MinCountConstraintComponent(SHACL.MIN_COUNT_CONSTRAINT_COMPONENT, ProducesValidationResultValue.NEVER),
21+
22+
DatatypeConstraintComponent(SHACL.DATATYPE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
23+
NodeKindConstraintComponent(SHACL.NODE_KIND_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
24+
ClassConstraintComponent(SHACL.CLASS_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
25+
26+
PatternConstraintComponent(SHACL.PATTERN_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
27+
UniqueLangConstraintComponent(SHACL.UNIQUE_LANG_CONSTRAINT_COMPONENT, ProducesValidationResultValue.NEVER),
28+
LanguageInConstraintComponent(SHACL.LANGUAGE_IN_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
29+
MaxLengthConstraintComponent(SHACL.MAX_LENGTH_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
30+
MinLengthConstraintComponent(SHACL.MIN_LENGTH_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
31+
32+
InConstraintComponent(SHACL.IN_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
33+
HasValueConstraintComponent(SHACL.HAS_VALUE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.NEVER),
34+
HasValueInConstraintComponent(DASH.HasValueInConstraintComponent, ProducesValidationResultValue.NEVER),
35+
ClosedConstraintComponent(SHACL.CLOSED_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
36+
37+
MinExclusiveConstraintComponent(SHACL.MIN_EXCLUSIVE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
38+
MaxExclusiveConstraintComponent(SHACL.MAX_EXCLUSIVE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
39+
MaxInclusiveConstraintComponent(SHACL.MAX_INCLUSIVE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
40+
MinInclusiveConstraintComponent(SHACL.MIN_INCLUSIVE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
41+
42+
AndConstraintComponent(SHACL.AND_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
43+
OrConstraintComponent(SHACL.OR_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
44+
NotConstraintComponent(SHACL.NOT_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
45+
XoneConstraintComponent(SHACL.XONE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
46+
47+
DisjointConstraintComponent(SHACL.DISJOINT_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
48+
EqualsConstraintComponent(SHACL.EQUALS_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
49+
LessThanConstraintComponent(SHACL.LESS_THAN_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
5050
LessThanOrEqualsConstraintComponent(SHACL.LESS_THAN_OR_EQUALS_CONSTRAINT_COMPONENT,
51-
true),
51+
ProducesValidationResultValue.ALWAYS),
5252

5353
QualifiedMaxCountConstraintComponent(SHACL.QUALIFIED_MAX_COUNT_CONSTRAINT_COMPONENT,
54-
false),
54+
ProducesValidationResultValue.NEVER),
5555
QualifiedMinCountConstraintComponent(SHACL.QUALIFIED_MIN_COUNT_CONSTRAINT_COMPONENT,
56-
false),
57-
NodeConstraintComponent(SHACL.NODE_CONSTRAINT_COMPONENT, true),
58-
PropertyConstraintComponent(SHACL.PROPERTY_CONSTRAINT_COMPONENT, false),
56+
ProducesValidationResultValue.NEVER),
57+
NodeConstraintComponent(SHACL.NODE_CONSTRAINT_COMPONENT, ProducesValidationResultValue.ALWAYS),
58+
PropertyConstraintComponent(SHACL.PROPERTY_CONSTRAINT_COMPONENT, ProducesValidationResultValue.NEVER),
5959

60-
SPARQLConstraintComponent(SHACL.SPARQL_CONSTRAINT_COMPONENT, true);
60+
SPARQLConstraintComponent(SHACL.SPARQL_CONSTRAINT_COMPONENT, ProducesValidationResultValue.SOMETIMES);
6161

6262
private final IRI iri;
63-
private final boolean producesValidationResultValue;
63+
private final ProducesValidationResultValue producesValidationResultValue;
6464

65-
SourceConstraintComponent(IRI iri, boolean producesValidationResultValue) {
65+
SourceConstraintComponent(IRI iri, ProducesValidationResultValue producesValidationResultValue) {
6666
this.iri = iri;
6767
this.producesValidationResultValue = producesValidationResultValue;
6868
}
@@ -72,6 +72,16 @@ public IRI getIri() {
7272
}
7373

7474
public boolean producesValidationResultValue() {
75-
return producesValidationResultValue;
75+
return producesValidationResultValue != ProducesValidationResultValue.NEVER;
76+
}
77+
78+
public boolean alwaysProducesValidationResultValue() {
79+
return producesValidationResultValue == ProducesValidationResultValue.ALWAYS;
80+
}
81+
82+
private enum ProducesValidationResultValue {
83+
ALWAYS,
84+
NEVER,
85+
SOMETIMES
7686
}
7787
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public ValidationQuery(Collection<Namespace> namespaces, String query, List<Vari
8383
propertyShapeWithValue = false;
8484
valueIndex = variables.size();
8585
assert constraintComponent == null
86-
|| !constraintComponent.getConstraintComponent().producesValidationResultValue();
86+
|| !constraintComponent.getConstraintComponent().alwaysProducesValidationResultValue();
8787
}
8888
} else {
8989
targetIndex = variables.size() - 1;

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

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.util.ArrayList;
1515
import java.util.List;
1616
import java.util.Objects;
17-
import java.util.Optional;
1817
import java.util.Set;
1918
import java.util.stream.Stream;
2019

@@ -37,10 +36,8 @@
3736
import org.eclipse.rdf4j.sail.shacl.ast.ValidationQuery;
3837
import org.eclipse.rdf4j.sail.shacl.ast.paths.Path;
3938
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.AllTargetsPlanNode;
40-
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BulkedExternalInnerJoin;
4139
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
4240
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider;
43-
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ShiftToPropertyShape;
4441
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.SparqlConstraintSelect;
4542
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Unique;
4643
import org.eclipse.rdf4j.sail.shacl.ast.targets.EffectiveTarget;
@@ -162,35 +159,45 @@ public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connections
162159

163160
PlanNode allTargets;
164161
if (overrideTargetNode != null) {
165-
allTargets = getPlanNodeForOverrideTargetNode(connectionsGroup, validationSettings, overrideTargetNode,
162+
allTargets = getPlanNodeForOverrideTargetNode(
163+
connectionsGroup,
164+
validationSettings,
165+
overrideTargetNode,
166166
scope,
167-
stableRandomVariableProvider, effectiveTarget, getTargetChain().getPath());
167+
effectiveTarget
168+
);
169+
168170
} else {
169171
allTargets = effectiveTarget.getAllTargets(connectionsGroup, validationSettings.getDataGraph(), scope);
170172
}
171173

174+
if (effectiveTarget.size() > 1) {
175+
allTargets = Unique.getInstance(allTargets, true);
176+
}
177+
172178
return new SparqlConstraintSelect(connectionsGroup.getBaseConnection(), allTargets, select, scope,
173179
validationSettings.getDataGraph(), produceValidationReports, this, shape);
174180

175181
}
176182

177183
private PlanNode getPlanNodeForOverrideTargetNode(ConnectionsGroup connectionsGroup,
178184
ValidationSettings validationSettings, PlanNodeProvider overrideTargetNode, Scope scope,
179-
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider, EffectiveTarget effectiveTarget,
180-
Optional<Path> path) {
185+
EffectiveTarget effectiveTarget) {
181186
PlanNode planNode;
187+
assert scope != null;
188+
189+
PlanNode overrideTargetPlanNode = overrideTargetNode.getPlanNode();
182190

183191
if (scope == Scope.nodeShape) {
184-
PlanNode overrideTargetPlanNode = overrideTargetNode.getPlanNode();
185192

186193
if (overrideTargetPlanNode instanceof AllTargetsPlanNode) {
187-
PlanNode allTargets = effectiveTarget.getAllTargets(connectionsGroup,
188-
validationSettings.getDataGraph(), scope);
189-
190-
return Unique.getInstance(allTargets, true);
194+
return effectiveTarget.getAllTargets(connectionsGroup, validationSettings.getDataGraph(), scope);
191195
} else {
192-
return effectiveTarget.extend(overrideTargetPlanNode, connectionsGroup,
193-
validationSettings.getDataGraph(), scope,
196+
return effectiveTarget.extend(
197+
overrideTargetPlanNode,
198+
connectionsGroup,
199+
validationSettings.getDataGraph(),
200+
scope,
194201
EffectiveTarget.Extend.right,
195202
false,
196203
null
@@ -199,36 +206,16 @@ private PlanNode getPlanNodeForOverrideTargetNode(ConnectionsGroup connectionsGr
199206
}
200207

201208
} else {
202-
PlanNode overrideTargetPlanNode = overrideTargetNode.getPlanNode();
203209

204210
if (overrideTargetPlanNode instanceof AllTargetsPlanNode) {
205-
// We are cheating a bit here by retrieving all the targets and values at the same time by
206-
// pretending to be in node shape scope and then shifting the results back to property shape scope
207-
PlanNode allTargets = getTargetChain()
208-
.getEffectiveTarget(Scope.nodeShape,
209-
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider)
210-
.getAllTargets(connectionsGroup, validationSettings.getDataGraph(), Scope.nodeShape);
211-
allTargets = new ShiftToPropertyShape(allTargets);
212-
213-
return Unique.getInstance(allTargets, true);
214-
211+
return effectiveTarget.getAllTargets(connectionsGroup, validationSettings.getDataGraph(), scope);
215212
} else {
216213

217214
overrideTargetPlanNode = effectiveTarget.extend(overrideTargetPlanNode, connectionsGroup,
218215
validationSettings.getDataGraph(), scope,
219216
EffectiveTarget.Extend.right, false, null);
220217

221-
planNode = new BulkedExternalInnerJoin(overrideTargetPlanNode,
222-
connectionsGroup.getBaseConnection(),
223-
validationSettings.getDataGraph(), path.get()
224-
.getTargetQueryFragment(new StatementMatcher.Variable("a"),
225-
new StatementMatcher.Variable("c"),
226-
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider,
227-
Set.of()),
228-
false, null,
229-
BulkedExternalInnerJoin.getMapper("a", "c", scope, validationSettings.getDataGraph())
230-
);
231-
planNode = connectionsGroup.getCachedNodeFor(planNode);
218+
planNode = connectionsGroup.getCachedNodeFor(overrideTargetPlanNode);
232219
}
233220
}
234221

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,29 +121,42 @@ private void calculateNext() {
121121
}
122122
}
123123

124-
Value value = bindingSet.getValue("value");
124+
Value value1 = bindingSet.getValue("value");
125+
if (value1 == null) {
126+
value1 = nextTarget.getValue();
127+
}
128+
Value currentValue = value1;
129+
125130
Value path = bindingSet.getValue("path");
126131

127132
if (scope == ConstraintComponent.Scope.nodeShape) {
128133
next = nextTarget.addValidationResult(t -> {
129-
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), value,
134+
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
135+
currentValue,
130136
shape,
131137
constraintComponent, shape.getSeverity(),
132138
ConstraintComponent.Scope.nodeShape, t.getContexts(),
133139
shape.getContexts());
134-
validationResult.setPathIri(path);
140+
if (path != null) {
141+
validationResult.setPathIri(path);
142+
}
135143
return validationResult;
136144
});
137145
} else {
138-
ValidationTuple validationTuple = new ValidationTuple(nextTarget.getActiveTarget(), value,
139-
scope, true, nextTarget.getContexts());
146+
147+
ValidationTuple validationTuple = new ValidationTuple(nextTarget.getActiveTarget(),
148+
currentValue,
149+
scope, currentValue != null, nextTarget.getContexts());
140150
next = ValidationTupleHelper.join(nextTarget, validationTuple).addValidationResult(t -> {
141-
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(), value,
151+
ValidationResult validationResult = new ValidationResult(t.getActiveTarget(),
152+
currentValue,
142153
shape,
143154
constraintComponent, shape.getSeverity(),
144155
ConstraintComponent.Scope.propertyShape, t.getContexts(),
145156
shape.getContexts());
146-
validationResult.setPathIri(path);
157+
if (path != null) {
158+
validationResult.setPathIri(path);
159+
}
147160
return validationResult;
148161
});
149162
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ public List<Value> getTargetChain(boolean includePropertyShapeValues) {
323323
}
324324

325325
public ValidationTuple setValue(Value value) {
326+
assert value != null;
326327
if (value.equals(getValue())) {
327328
return this;
328329
}
@@ -493,7 +494,9 @@ public ValidationTuple join(ValidationTuple right) {
493494
ValidationTuple validationTuple = new ValidationTuple(validationResults, chain, scope,
494495
propertyShapeScopeWithValue, compressedTuples, contexts);
495496
if (scope == ConstraintComponent.Scope.propertyShape) {
496-
validationTuple = validationTuple.setValue(right.getValue());
497+
if (right.hasValue()) {
498+
validationTuple = validationTuple.setValue(right.getValue());
499+
}
497500
}
498501

499502
for (ValidationResult validationResult : right.getValidationResult()) {

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/ValidationResult.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,16 @@ public ValidationResult(Value focusNode, Value value, Shape shape,
7777
this.shape = shape;
7878

7979
if (sourceConstraintComponent.producesValidationResultValue()) {
80-
assert value != null;
80+
assert !sourceConstraintComponent.alwaysProducesValidationResultValue() || value != null;
8181

8282
// value could be null if assertions are disabled
8383
// noinspection ConstantValue
84-
if (value == null) {
84+
if (value == null && sourceConstraintComponent.alwaysProducesValidationResultValue()) {
8585
logger.error(
8686
"Source constraint component {} was expected to produce a value, but value is null! Shape: {}",
8787
sourceConstraintComponent, shape);
8888
}
8989

90-
// value could be null if assertions are disabled
91-
// noinspection OptionalOfNullableMisuse
9290
this.value = Optional.ofNullable(value);
9391
} else {
9492
assert scope != ConstraintComponent.Scope.propertyShape || value == null;

0 commit comments

Comments
 (0)