Skip to content

Commit

Permalink
GH-2458 sh:hasValue and dash:hasValueIn support as node shapes (#2459)
Browse files Browse the repository at this point in the history
* #2458 initial test cases

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* more test cases

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* GH-2458 initial fix

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* GH-2458 better fix

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* more passing tests

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* GH-2458 all tests pass

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* fixed based on review

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>

* GH-2458 added some more test cases and fixed them

Signed-off-by: Håvard Ottestad <hmottestad@gmail.com>
  • Loading branch information
hmottestad committed Sep 14, 2020
1 parent 484220e commit 4d5f6ea
Show file tree
Hide file tree
Showing 100 changed files with 1,495 additions and 118 deletions.
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.rdf4j.sail.shacl.ShaclSail;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.Stats;
import org.eclipse.rdf4j.sail.shacl.planNodes.AbstractBulkJoinPlanNode;
import org.eclipse.rdf4j.sail.shacl.planNodes.AggregateIteratorTypeOverride;
import org.eclipse.rdf4j.sail.shacl.planNodes.EnrichWithShape;
import org.eclipse.rdf4j.sail.shacl.planNodes.IteratorData;
Expand Down Expand Up @@ -214,6 +215,12 @@ public boolean childrenHasOwnPath() {
.anyMatch(a -> a);
}

public boolean childrenHasOwnPathRecursive() {
return and.stream()
.flatMap(a -> a.stream().map(PathPropertyShape::childrenHasOwnPathRecursive))
.anyMatch(a -> a);
}

@Override
public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, boolean negated) {
Optional<PlanNode> reduce = and
Expand All @@ -227,15 +234,56 @@ public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, boolean neg

@Override
public String buildSparqlValidNodes(String targetVar) {
return and.stream()
.map(propertyShapes -> propertyShapes
.stream()
.map(propertyShape -> propertyShape.buildSparqlValidNodes(targetVar))
.reduce((a, b) -> a + "\n" + b))
.filter(Optional::isPresent)
.map(Optional::get)
.reduce((a, b) -> a + "\n" + b)
.orElse("");

if (hasOwnPath()) {
// within property shape
String objectVariable = randomVariable();
String pathQuery1 = getPath().getQuery(targetVar, objectVariable, null);

String collect = and.stream()
.map(l -> l.stream()
.map(p -> p.buildSparqlValidNodes(objectVariable))
.reduce((a, b) -> a + " && " + b))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.joining(" ) && ( ", "( ",
" )"));

String query = pathQuery1 + "\n FILTER (! EXISTS {\n" + pathQuery1.replaceAll("(?m)^", "\t")
+ "\n\tFILTER(!(" + collect + "))\n})";

String pathQuery2 = getPath().getQuery(targetVar, randomVariable(), null);

query = "{\n" + AbstractBulkJoinPlanNode.VALUES_INJECTION_POINT + "\n " + query.replaceAll("(?m)^", "\t")
+ " \n} UNION {\n\t" + AbstractBulkJoinPlanNode.VALUES_INJECTION_POINT + "\n\t" + targetVar + " "
+ randomVariable() + " "
+ randomVariable() + ".\n\tFILTER(NOT EXISTS {\n " + pathQuery2.replaceAll("(?m)^", "\t")
+ " \n})\n}";

return query;
} else if (!childrenHasOwnPathRecursive()) {

return and.stream()
.map(l -> l.stream()
.map(p -> p.buildSparqlValidNodes(targetVar))
.reduce((a, b) -> a + " && " + b))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.joining(" ) && ( ", "( ",
" )"));
} else {
// within node shape
return and.stream()
.map(propertyShapes -> propertyShapes
.stream()
.map(propertyShape -> propertyShape.buildSparqlValidNodes(targetVar))
.reduce((a, b) -> a + "\n" + b))
.filter(Optional::isPresent)
.map(Optional::get)
.reduce((a, b) -> a + "\n" + b)
.orElse("");
}

}

@Override
Expand Down
Expand Up @@ -7,6 +7,8 @@
*******************************************************************************/
package org.eclipse.rdf4j.sail.shacl.AST;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
Expand All @@ -22,6 +24,8 @@
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.sail.shacl.ConnectionsGroup;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.planNodes.AbstractBulkJoinPlanNode;
import org.eclipse.rdf4j.sail.shacl.planNodes.BulkedExternalInnerJoin;
import org.eclipse.rdf4j.sail.shacl.planNodes.EnrichWithShape;
import org.eclipse.rdf4j.sail.shacl.planNodes.ExternalFilterByQuery;
import org.eclipse.rdf4j.sail.shacl.planNodes.PlanNode;
Expand Down Expand Up @@ -63,31 +67,68 @@ public PlanNode getPlan(ConnectionsGroup connectionsGroup, boolean printPlans,
}
assert !negateSubPlans : "There are no subplans!";

if (getPath() == null) {
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}
if (!hasOwnPath()) {

PlanNode invalidTargets = new TupleMapper(addedTargets, t -> {
List<Value> line = t.getLine();
t.getLine().add(line.get(0));
return t;
});
if (getPath() == null) {
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}

PlanNode invalidTargets = new TupleMapper(addedTargets, t -> {
List<Value> line = t.getLine();
t.getLine().add(line.get(0));
return t;
});

if (negateThisPlan) {
invalidTargets = new ValueInFilter(invalidTargets, hasValueIn)
.getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(invalidTargets, hasValueIn)
.getFalseNode(UnBufferedPlanNode.class);
}

if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}

return new EnrichWithShape(invalidTargets, this);

if (negateThisPlan) {
invalidTargets = new ValueInFilter(invalidTargets, hasValueIn).getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(invalidTargets, hasValueIn).getFalseNode(UnBufferedPlanNode.class);
}

if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
PlanNode addedByPath = getPath().getPlanAddedStatements(connectionsGroup, null);
addedTargets = new UnionNode(new TrimTuple(addedByPath, 0, 1), addedTargets);
addedTargets = new Unique(addedTargets);

addedTargets = nodeShape.getTargetFilter(connectionsGroup, addedTargets);

if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}

return new EnrichWithShape(invalidTargets, this);
PlanNode joined = new BulkedExternalInnerJoin(addedTargets, connectionsGroup.getBaseConnection(),
getPath().getQuery("?a", "?c", null), false, null, "?a", "?c");

PlanNode invalidTargets;
if (negateThisPlan) {
invalidTargets = new ValueInFilter(joined, hasValueIn)
.getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(joined, hasValueIn)
.getFalseNode(UnBufferedPlanNode.class);
}

if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}

return new EnrichWithShape(invalidTargets, this);

}
}

// TODO - these plans are generally slow because they generate a lot of SPARQL queries and also they don't
Expand Down Expand Up @@ -167,25 +208,46 @@ public SourceConstraintComponent getSourceConstraintComponent() {
@Override
public String buildSparqlValidNodes(String targetVar) {

return hasValueIn
.stream()
.map(value -> {
String objectVar = "?hasValueIn_" + UUID.randomUUID().toString().replace("-", "");

if (value instanceof IRI) {
return "BIND(<" + value + "> as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}
if (value instanceof Literal) {
return "BIND(" + value.toString() + " as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}

throw new UnsupportedOperationException(
"value was unsupported type: " + value.getClass().getSimpleName());
})
.collect(Collectors.joining("} UNION {\n#VALUES_INJECTION_POINT#\n", "{\n#VALUES_INJECTION_POINT#\n",
"}"));
if (hasOwnPath()) {
return hasValueIn
.stream()
.map(value -> {
String objectVar = "?hasValueIn_" + UUID.randomUUID().toString().replace("-", "");

if (value instanceof IRI) {
return "BIND(<" + value + "> as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}
if (value instanceof Literal) {
return "BIND(" + value.toString() + " as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}

throw new UnsupportedOperationException(
"value was unsupported type: " + value.getClass().getSimpleName());
})
.collect(
Collectors.joining("} UNION {\n" + AbstractBulkJoinPlanNode.VALUES_INJECTION_POINT + "\n",
"{\n" + AbstractBulkJoinPlanNode.VALUES_INJECTION_POINT + "\n",
"}"));

} else {

return hasValueIn
.stream()
.map(value -> {
if (value instanceof IRI) {
return targetVar + " = <" + value + ">";
} else if (value instanceof Literal) {
return targetVar + " = " + value;
}
throw new UnsupportedOperationException(
"value was unsupported type: " + value.getClass().getSimpleName());
})
.reduce((a, b) -> a + " || " + b)
.orElseThrow(() -> new IllegalStateException("hasValueIn was empty"));

}

}

Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.sail.shacl.ConnectionsGroup;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.planNodes.BulkedExternalInnerJoin;
import org.eclipse.rdf4j.sail.shacl.planNodes.EnrichWithShape;
import org.eclipse.rdf4j.sail.shacl.planNodes.ExternalFilterByQuery;
import org.eclipse.rdf4j.sail.shacl.planNodes.PlanNode;
Expand Down Expand Up @@ -70,33 +71,69 @@ public PlanNode getPlan(ConnectionsGroup connectionsGroup, boolean printPlans,
// TODO - these plans are generally slow because they generate a lot of SPARQL queries and also they don't
// optimize for the case when everything already valid in the added statements.

if (getPath() == null) {
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}

PlanNode invalidTargets = new TupleMapper(addedTargets, t -> {
List<Value> line = t.getLine();
t.getLine().add(line.get(0));
return t;
});
if (!hasOwnPath()) {
if (getPath() == null) {
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}

PlanNode invalidTargets = new TupleMapper(addedTargets, t -> {
List<Value> line = t.getLine();
t.getLine().add(line.get(0));
return t;
});

if (negateThisPlan) {
invalidTargets = new ValueInFilter(invalidTargets,
new HashSet<>(Collections.singletonList(hasValue)))
.getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(invalidTargets,
new HashSet<>(Collections.singletonList(hasValue)))
.getFalseNode(UnBufferedPlanNode.class);
}

if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}

return new EnrichWithShape(invalidTargets, this);

if (negateThisPlan) {
invalidTargets = new ValueInFilter(invalidTargets, new HashSet<>(Collections.singletonList(hasValue)))
.getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(invalidTargets, new HashSet<>(Collections.singletonList(hasValue)))
.getFalseNode(UnBufferedPlanNode.class);
}

if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}
PlanNode addedTargets = nodeShape.getPlanAddedStatements(connectionsGroup, null);
PlanNode addedByPath = getPath().getPlanAddedStatements(connectionsGroup, null);
addedTargets = new UnionNode(new TrimTuple(addedByPath, 0, 1), addedTargets);
addedTargets = new Unique(addedTargets);

addedTargets = nodeShape.getTargetFilter(connectionsGroup, addedTargets);

if (overrideTargetNode != null) {
addedTargets = overrideTargetNode.getPlanNode();
}

PlanNode joined = new BulkedExternalInnerJoin(addedTargets, connectionsGroup.getBaseConnection(),
getPath().getQuery("?a", "?c", null), false, null, "?a", "?c");

PlanNode invalidTargets;
if (negateThisPlan) {
invalidTargets = new ValueInFilter(joined, new HashSet<>(Collections.singletonList(hasValue)))
.getTrueNode(UnBufferedPlanNode.class);
} else {
invalidTargets = new ValueInFilter(joined, new HashSet<>(Collections.singletonList(hasValue)))
.getFalseNode(UnBufferedPlanNode.class);
}

return new EnrichWithShape(invalidTargets, this);
if (printPlans) {
String planAsGraphvizDot = getPlanAsGraphvizDot(invalidTargets, connectionsGroup);
logger.info(planAsGraphvizDot);
}

return new EnrichWithShape(invalidTargets, this);

}
}

if (overrideTargetNode != null) {
Expand Down Expand Up @@ -206,14 +243,27 @@ public Value getHasValue() {

@Override
public String buildSparqlValidNodes(String targetVar) {
String objectVar = "?hasValue_" + UUID.randomUUID().toString().replace("-", "");

if (hasValue instanceof IRI) {
return "BIND(<" + hasValue + "> as " + objectVar + ")\n" + getPath().getQuery(targetVar, objectVar, null);
}
if (hasValue instanceof Literal) {
return "BIND(" + hasValue.toString() + " as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
if (hasOwnPath()) {
String objectVar = "?hasValue_" + UUID.randomUUID().toString().replace("-", "");

if (hasValue instanceof IRI) {
return "BIND(<" + hasValue + "> as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}
if (hasValue instanceof Literal) {
return "BIND(" + hasValue.toString() + " as " + objectVar + ")\n"
+ getPath().getQuery(targetVar, objectVar, null);
}
} else {

if (hasValue instanceof IRI) {
return targetVar + " = <" + hasValue + ">";
}
if (hasValue instanceof Literal) {
return targetVar + " = " + hasValue.toString();

}
}

throw new UnsupportedOperationException(
Expand Down

0 comments on commit 4d5f6ea

Please sign in to comment.